Skip to content

Rewrite equivalence tests and fix thread leak in EolParallelContext #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.eclipse.epsilon.common.function.BaseDelegate.MergeMode;

import org.eclipse.epsilon.common.concurrent.ConcurrencyUtils;
import org.eclipse.epsilon.common.concurrent.DelegatePersistentThreadLocal;
import org.eclipse.epsilon.common.concurrent.PersistentThreadLocal;
import org.eclipse.epsilon.common.function.BaseDelegate;
import org.eclipse.epsilon.common.function.BaseDelegate.MergeMode;
import org.eclipse.epsilon.common.module.ModuleElement;
import org.eclipse.epsilon.eol.IEolModule;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.concurrent.EolNestedParallelismException;
import org.eclipse.epsilon.eol.execute.ExecutorFactory;
import org.eclipse.epsilon.eol.execute.concurrent.EolThreadPoolExecutor;
import org.eclipse.epsilon.eol.execute.context.EolContext;
import org.eclipse.epsilon.eol.execute.context.FrameStack;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.operations.contributors.OperationContributorRegistry;
import org.eclipse.epsilon.eol.IEolModule;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.concurrent.EolNestedParallelismException;
import org.eclipse.epsilon.eol.execute.concurrent.EolThreadPoolExecutor;

/**
* Skeletal implementation of a parallel IEolContext. This class takes care of
Expand Down Expand Up @@ -198,7 +199,19 @@ protected void nullifyThreadLocals() {

protected void clearExecutor() {
if (executorService != null) {
executorService.shutdownNow();
/*
* Note: we need to shut down the executor service no matter what, in order to
* avoid leaking threads. The only difference is whether we interrupt running
* tasks or not.
*/
if (this.isInShortCircuitTask) {
// Interrupt tasks
executorService.shutdownNow();
} else {
// Don't interrupt, but stop accepting new tasks
executorService.shutdown();
}

executorService = null;
}
}
Expand All @@ -221,9 +234,7 @@ public synchronized ExecutorService beginParallelTask(ModuleElement entryPoint,

@Override
public synchronized void endParallelTask() throws EolRuntimeException {
if (isInShortCircuitTask) {
clearExecutor();
}
clearExecutor();
clearThreadLocals();
isInParallelTask = false;
isInShortCircuitTask = false;
Expand Down Expand Up @@ -253,8 +264,8 @@ public final int getParallelism() {
}

@Override
public final EolThreadPoolExecutor getExecutorService() {
if (executorService == null) synchronized (this) {
public synchronized final EolThreadPoolExecutor getExecutorService() {
if (executorService == null) {
executorService = newExecutorService();
}
return executorService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ default void ensureNotNested(ModuleElement entryPoint) throws EolNestedParalleli
*
* @param entryPoint The AST to associate with this task. May be null, in which
* case a default value (e.g. {@linkplain #getModule()}) should be used.
* @param shortCircuiting Whether the task may be terminated abruptly.
* @param canTerminate Whether the task may be terminated abruptly.
* @return {@link #getExecutorService()}
* @throws EolNestedParallelismException If there was already a parallel task in progress.
*/
default ExecutorService beginParallelTask(ModuleElement entryPoint, boolean shortCircuiting) throws EolNestedParallelismException {
default ExecutorService beginParallelTask(ModuleElement entryPoint, boolean canTerminate) throws EolNestedParallelismException {
ensureNotNested(entryPoint != null ? entryPoint : getModule());
ExecutorService executor = getExecutorService();
assert executor != null && !executor.isShutdown();
Expand Down Expand Up @@ -149,12 +149,12 @@ default <T> List<T> executeAll(ModuleElement entryPoint, Collection<? extends Ca
for (Future<? extends T> future : executor.invokeAll(jobs)) {
results.add(future.get());
}
}
catch (InterruptedException | ExecutionException ex) {
} catch (InterruptedException | ExecutionException ex) {
EolRuntimeException.propagateDetailed(ex);
assert false : "This should never be reached";
} finally {
endParallelTask();
}
endParallelTask();
return results;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import org.eclipse.epsilon.ecl.engine.test.acceptance.builtins.EclCanAccessBuiltinsTests;
import org.eclipse.epsilon.ecl.engine.test.acceptance.domain.DomainTests;
import org.eclipse.epsilon.ecl.engine.test.acceptance.equivalence.EclModuleEquivalenceTests;
import org.eclipse.epsilon.ecl.engine.test.acceptance.matches.MatchesOperationTest;
import org.eclipse.epsilon.ecl.engine.test.acceptance.trace.MatchTraceTest;
import org.eclipse.epsilon.ecl.engine.test.acceptance.trees.TestXmlTreeComparison;
Expand All @@ -24,7 +25,8 @@
@SuiteClasses({
EclCanAccessBuiltinsTests.class, TestXmlTreeComparison.class,
MatchesOperationTest.class, DomainTests.class,
EclUnparserTests.class, MatchTraceTest.class
EclUnparserTests.class, MatchTraceTest.class,
EclModuleEquivalenceTests.class
})
public class EclAcceptanceTestSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
package org.eclipse.epsilon.ecl.engine.test.acceptance;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import org.eclipse.epsilon.common.util.FileUtil;
import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.ecl.*;
import org.eclipse.epsilon.ecl.concurrent.*;
import org.eclipse.epsilon.ecl.EclModule;
import org.eclipse.epsilon.ecl.IEclModule;
import org.eclipse.epsilon.ecl.concurrent.EclModuleParallelAnnotation;
import org.eclipse.epsilon.ecl.engine.test.acceptance.matches.MatchesOperationTest;
import org.eclipse.epsilon.ecl.execute.context.concurrent.EclContextParallel;
import org.eclipse.epsilon.ecl.launch.EclRunConfiguration;
import org.eclipse.epsilon.emc.emf.EmfModel;
Expand All @@ -32,29 +36,39 @@ protected EclAcceptanceTestUtil() {}

static final String testsBase = getTestBaseDir(EclAcceptanceTestUtil.class);

public static Collection<EclRunConfiguration> getScenarios(Iterable<Supplier<? extends IEclModule>> moduleGetters) throws Exception {
ArrayList<EclRunConfiguration> scenarios = new ArrayList<>();

String matchesRoot = testsBase+"/matches/";
Path matchesMM = Paths.get(matchesRoot+"mymetamodel.ecore");
public static Collection<Supplier<EclRunConfiguration>> getScenarioSuppliers(Iterable<Supplier<? extends IEclModule>> moduleGetters) throws Exception {
List<Supplier<EclRunConfiguration>> scenarios = new ArrayList<>();

Path matchesMM = FileUtil.getFileStandalone("mymetamodel.ecore", MatchesOperationTest.class).toPath();
Path leftModel = FileUtil.getFileStandalone("Left.model", MatchesOperationTest.class).toPath();
Path rightModel = FileUtil.getFileStandalone("Right.model", MatchesOperationTest.class).toPath();
Path script = FileUtil.getFileStandalone("CompareInstance.ecl", MatchesOperationTest.class).toPath();
// TODO - what to do with CompareInstanceParallel.ecl?

StringProperties
leftModelProps = createModelProperties(matchesMM, Paths.get(matchesRoot+"Left.model")),
rightModelProps = createModelProperties(matchesMM, Paths.get(matchesRoot+"Right.model"));
leftModelProps = createModelProperties(leftModel, matchesMM),
rightModelProps = createModelProperties(rightModel, matchesMM);

for (Supplier<? extends IEclModule> moduleSup : moduleGetters) {
scenarios.add(EclRunConfiguration.Builder()
.withScript(matchesRoot+"CompareInstance")
scenarios.add(() -> EclRunConfiguration.Builder()
.withScript(script)
.withModel(new EmfModel(), leftModelProps)
.withModel(new EmfModel(), rightModelProps)
.withModule(moduleSup.get())
.build()
);
}

return scenarios;
}

public static Collection<Supplier<? extends IEclModule>> modules() {
return parallelModules(THREADS, EclModule::new, p -> new EclModuleParallelAnnotation(new EclContextParallel(p)));
return modules(true);
}

public static Collection<Supplier<? extends IEclModule>> modules(boolean includeStandard) {
return parallelModules(THREADS,
includeStandard ? EclModule::new : null,
p -> new EclModuleParallelAnnotation(new EclContextParallel(p)));
}
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,59 @@
/*********************************************************************
* Copyright (c) 2018 The University of York.
/*******************************************************************************
* Copyright (c) 2024 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
*******************************************************************************/
package org.eclipse.epsilon.ecl.engine.test.acceptance.equivalence;

import static org.eclipse.epsilon.ecl.engine.test.acceptance.EclAcceptanceTestUtil.*;
import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

import org.eclipse.epsilon.ecl.EclModule;
import org.eclipse.epsilon.ecl.engine.test.acceptance.EclAcceptanceTestUtil;
import org.eclipse.epsilon.ecl.launch.EclRunConfiguration;
import org.eclipse.epsilon.ecl.trace.MatchTrace;
import org.eclipse.epsilon.eol.engine.test.acceptance.util.EolEquivalenceTests;
import org.eclipse.epsilon.test.util.EpsilonTestUtil;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.eclipse.epsilon.eol.engine.test.acceptance.util.AbstractEolEquivalenceTests;
import org.junit.runners.Parameterized.Parameters;

/**
*
* @author Sina Madani
* @since 1.6
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class EclModuleEquivalenceTests extends EolEquivalenceTests<EclRunConfiguration> {
public class EclModuleEquivalenceTests extends AbstractEolEquivalenceTests<EclRunConfiguration> {

public EclModuleEquivalenceTests(EclRunConfiguration configUnderTest) {
super(configUnderTest);
public EclModuleEquivalenceTests(EquivalenceTestParameters<EclRunConfiguration> params) {
super(params);
}

/**
* @return A collection of pre-configured run configurations, each with their own IEclModule.
* @throws Exception
*/
@Parameters//(name = "0") Don't use this as the Eclipse JUnit view won't show failures!
public static Iterable<? extends EclRunConfiguration> configurations() throws Exception {
return getScenarios(modules());
}

@BeforeClass
public static void setUpBeforeClass() throws Exception {
setUpEquivalenceTest(getScenarios(Collections.singleton(EclModule::new)));
}

@Override
public void _0test0() throws Exception {
super.beforeTests();
protected void assertEquivalentConfigurations(EclRunConfiguration oracleConfig, EclRunConfiguration testedConfig) throws Exception {
super.assertEquivalentConfigurations(oracleConfig, testedConfig);

assertEquals("Match traces should be equal",
oracleConfig.getResult(),
testedConfig.getResult());
}

@Test
public void testMatchResults() throws Exception {
MatchTrace
expectedTrace = expectedConfig.getResult(),
actualTrace = testConfig.getResult();

EpsilonTestUtil.testCollectionsHaveSameElements(expectedTrace, actualTrace, "MatchTrace");
@Parameters
public static Collection<EquivalenceTestParameters<EclRunConfiguration>> configurations() throws Exception {
Collection<Supplier<EclRunConfiguration>> oracleSuppliers = EclAcceptanceTestUtil.getScenarioSuppliers(Collections.singleton(EclModule::new));
Collection<Supplier<EclRunConfiguration>> testSuppliers = EclAcceptanceTestUtil.getScenarioSuppliers(EclAcceptanceTestUtil.modules(false));

int i = 1;
List<EquivalenceTestParameters<EclRunConfiguration>> configs = new ArrayList<>();
for (Supplier<EclRunConfiguration> oracleSupplier : oracleSuppliers) {
for (Supplier<EclRunConfiguration> testSupplier : testSuppliers) {
configs.add(new EquivalenceTestParameters<>(
"test" + i++,
oracleSupplier,
testSupplier
));
}
}
return configs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
import org.eclipse.epsilon.egx.engine.test.acceptance.extendedproperties.ExtendedPropertiesTests;
import org.eclipse.epsilon.egx.engine.test.acceptance.formatter.FormatterTests;
import org.eclipse.epsilon.egx.engine.test.acceptance.hutn.EgxHutnTestSuite;
import org.eclipse.epsilon.egx.engine.test.acceptance.operations.*;
import org.eclipse.epsilon.egx.engine.test.acceptance.operations.IncludeTests;
import org.eclipse.epsilon.egx.engine.test.acceptance.operations.PrintTests;
import org.eclipse.epsilon.egx.engine.test.acceptance.parse.GenerationRuleConstructsTests;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import org.eclipse.epsilon.common.util.FileUtil;
import org.eclipse.epsilon.common.util.Multimap;
import org.eclipse.epsilon.egl.EgxModule;
import org.eclipse.epsilon.egl.IEgxModule;
import org.eclipse.epsilon.egl.concurrent.*;
import org.eclipse.epsilon.egl.concurrent.EgxModuleParallelAnnotation;
import org.eclipse.epsilon.egl.concurrent.EgxModuleParallelGenerationRuleAtoms;
import org.eclipse.epsilon.egl.execute.context.concurrent.EgxContextParallel;
import org.eclipse.epsilon.egl.launch.EgxRunConfiguration;
import org.eclipse.epsilon.eol.engine.test.acceptance.util.EolAcceptanceTestUtil;
Expand Down Expand Up @@ -69,13 +72,13 @@ public static Collection<Supplier<? extends IEgxModule>> modules(boolean include
p -> new EgxModuleParallelGenerationRuleAtoms(new EgxContextParallel(p))
);
}
public static Collection<EgxRunConfiguration> getScenarios(
List<String[]> testInputs,
Collection<Supplier<? extends IEgxModule>> moduleGetters) throws Exception {
return getScenarios(EgxRunConfiguration.class, testInputs, moduleGetters, null, EgxAcceptanceTestUtil.class);

public static Multimap<String, Supplier<EgxRunConfiguration>> getScenarioSuppliers(
List<String[]> testInputs,
Collection<Supplier<? extends IEgxModule>> moduleGetters) throws Exception {
return getScenarioSuppliers(EgxRunConfiguration.class, testInputs, moduleGetters, null, EgxAcceptanceTestUtil.class);
}

public static void deleteOutputDirectories() throws IOException {
try {
FileUtil.deleteDirectory(ecoreBase+"output");
Expand Down
Loading