blob: 8314342f12b220b9ad882303225792d8659dbce5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Aston University.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
*
* Contributors:
* Antonio Garcia-Dominguez - initial API and implementation
******************************************************************************/
package org.hawk.integration.tests.emf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.hawk.backend.tests.BackendTestSuite;
import org.hawk.backend.tests.factories.IGraphDatabaseFactory;
import org.hawk.core.query.IQueryEngine;
import org.hawk.epsilon.emc.EOLQueryEngine;
import org.hawk.graph.syncValidationListener.SyncValidationListener;
import org.hawk.graph.syncValidationListener.SyncValidationListener.ValidationError;
import org.hawk.integration.tests.ModelIndexingTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runners.Parameterized.Parameters;
/**
* Integration test case that indexes a fragmented version of the GraBaTs'09
* set0 model, and runs queries on containment subtrees with the
* {@link IQueryEngine#PROPERTY_SUBTREECONTEXT} through
* {@link EOLQueryEngine#getAllOf(String, String, String)} and
* {@link EOLQueryEngine#getAllOfKind(String)}. Has a small program to do the
* fragmentation at multiple levels.
*/
public class SubtreeContextTest extends ModelIndexingTest {
private static class Fragmenter {
private static final String pathToMetamodel = "resources/metamodels/JDTAST.ecore";
private static final String pathToOriginal = "resources/models/set0/set0.xmi";
private static final String pathToFragmented = "resources/models/set0-fragmented/set0.xmi";
private ResourceSetImpl rs;
public void run() throws IOException {
rs = new ResourceSetImpl();
rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl());
Resource rEcoreMM = rs.createResource(URI.createFileURI(new File(pathToMetamodel).getAbsolutePath()));
rEcoreMM.load(null);
for (EObject rawEPackage : rEcoreMM.getContents()) {
EPackage epackage = (EPackage) rawEPackage;
rs.getPackageRegistry().put(epackage.getNsURI(), epackage);
}
rs.getResources().remove(rEcoreMM);
final File fOriginal = new File(pathToOriginal);
Resource rRoot = rs.createResource(URI.createFileURI(fOriginal.getAbsolutePath()));
rRoot.load(null);
final File fFragmented = new File(pathToFragmented);
if (fFragmented.getParentFile().exists()) {
FileUtils.deleteDirectory(fFragmented.getParentFile());
}
rRoot.setURI(URI.createFileURI(fFragmented.getAbsolutePath()));
fragment(rRoot);
for (Resource r : rs.getResources()) {
r.save(null);
}
for (Resource r : rs.getResources()) {
r.unload();
}
rs = null;
}
private void fragment(Resource rRoot) {
File fRoot = new File(rRoot.getURI().toFileString());
File fRootFolder = fRoot.getParentFile();
for (TreeIterator<EObject> itContents = rRoot.getAllContents(); itContents.hasNext();) {
EObject eob = itContents.next();
if (rRoot.getContents().contains(eob)) {
// No roots (otherwise, we'd have endless recursion)
continue;
}
final String eClassName = eob.eClass().getName();
switch (eClassName) {
case "IJavaProject":
case "BinaryPackageFragmentRoot":
case "IPackageFragment":
EStructuralFeature sf = eob.eClass().getEStructuralFeature("elementName");
String name = eob.eGet(sf).toString();
final File fFolder = new File(fRootFolder, String.format("%s_%s", eClassName, name));
fFolder.mkdirs();
final File fChild = new File(fFolder, String.format("%s_%s.xmi", eClassName, name));
final URI uriChild = URI.createFileURI(fChild.getAbsolutePath());
Resource r = rs.createResource(uriChild);
itContents.prune();
r.getContents().add(eob);
fragment(r);
break;
}
}
}
}
@Rule
public GraphChangeListenerRule<SyncValidationListener> syncValidation = new GraphChangeListenerRule<>(
new SyncValidationListener());
@Rule
public TemporaryFolder modelFolder = new TemporaryFolder();
private File folderOriginal, folderFragmented;
private String originalRepoURI, fragmentedRepoURI;
@Parameters(name = "{0}")
public static Iterable<Object[]> params() {
return BackendTestSuite.caseParams();
}
public SubtreeContextTest(IGraphDatabaseFactory dbf) {
super(dbf, new EMFModelSupportFactory());
}
@Before
public void setUp() throws Throwable {
// Skip for deprecated backends - it is an expensive test
assumeFalse(db.getClass().isAnnotationPresent(Deprecated.class));
indexer.registerMetamodels(new File("resources/metamodels/Ecore.ecore"),
new File("resources/metamodels/JDTAST.ecore"));
folderOriginal = new File("resources/models/set0").getAbsoluteFile();
folderFragmented = new File("resources/models/set0-fragmented").getAbsoluteFile();
originalRepoURI = folderOriginal.toPath().toUri().toString();
fragmentedRepoURI = folderFragmented.toPath().toUri().toString();
}
protected void prepareBoth() throws Exception, Throwable {
prepareOriginal();
prepareFragmented();
}
protected void prepareFragmented() throws Exception, Throwable {
requestFolderIndex(folderFragmented);
scheduleAndWait(() -> {
assertNoValidationErrors("There should be no validation errors on the fragmented model");
return null;
});
}
private void assertNoValidationErrors(String message) {
List<ValidationError> errors = syncValidation.getListener().getErrors();
if (!errors.isEmpty()) {
List<String> messages = errors.stream()
.map(e -> e.getMessage())
.collect(Collectors.toList());
fail(message + ":\n" + String.join("\n\n", messages));
}
}
protected void prepareOriginal() throws Exception, Throwable {
requestFolderIndex(folderOriginal);
scheduleAndWait(() -> {
assertNoValidationErrors("There should be no validation errors on the original model");
return null;
});
}
@Test
public void allContents() throws Throwable {
prepareBoth();
// Sanity check for .allContents (repo-based)
final int originalSize = (int) eol("return Model.allContents.size;",
ctx(IQueryEngine.PROPERTY_REPOSITORYCONTEXT, originalRepoURI));
final int fragmentedSize = (int) eol("return Model.allContents.size;",
ctx(IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI));
assertEquals(originalSize, fragmentedSize);
assertEquals(originalSize * 2, eol("return Model.allContents.size;"));
// Now doing it with the subtree context (will be replaced by a breadth-first traversal)
final int subtreeOriginalSize = (int) eol("return Model.allContents.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, originalRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/set0.xmi"
));
assertEquals(originalSize, subtreeOriginalSize);
final int subtreeFragmentedSize = (int) eol("return Model.allContents.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/set0.xmi"
));
assertEquals(subtreeFragmentedSize, subtreeOriginalSize);
// Now limit to the contents of the first IJavaProject
final int originalJavaContents = (int) eol("return 1 + IJavaProject.all.first.closure(e|e.eContents).size;",
ctx(IQueryEngine.PROPERTY_REPOSITORYCONTEXT, originalRepoURI));
final int fileBasedJavaContents = (int) eol("return Model.allContents.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_FILECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/*"));
final int subtreeJavaContents = (int) eol("return Model.allContents.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi"
));
assertEquals(originalJavaContents, fileBasedJavaContents);
assertEquals(originalJavaContents, subtreeJavaContents);
}
@Test
public void getAllOf() throws Throwable {
prepareBoth();
final String eolQuery = "return IType.all.size;";
/*
* First do the most straightforward approach: find all the files under a folder,
* go to the type, then go through all instances filtering by file.
*/
final int fileClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_FILECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/*"));
/* This version goes to the file nodes first, then checks types. */
final int fileFirstClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_FILECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/*",
IQueryEngine.PROPERTY_FILEFIRST, "true"));
assertEquals(fileClasses, fileFirstClasses);
/* This version finds out the files in a containment subtree. */
final int subtreeClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi"
));
assertEquals(fileClasses, subtreeClasses);
/* This version finds out the files in a containment subtree, and uses file-first traversal. */
final int subtreeFileFirstClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi",
IQueryEngine.PROPERTY_FILEFIRST, "true"
));
assertEquals(fileClasses, subtreeFileFirstClasses);
/* This version finds out the files in a containment subtree, and registers derived edges to quickly find instances of a type within a containment subtree. */
final int subtreeClassesDerived = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi",
IQueryEngine.PROPERTY_SUBTREE_DERIVEDALLOF, "true"
));
assertEquals(fileClasses, subtreeClassesDerived);
}
@Test
public void getAllOfCountsSubtypes() throws Throwable {
prepareFragmented();
/* This version finds out the files in a containment subtree, and registers derived edges to quickly find instances of a type within a containment subtree. */
final int binpkgCount = (int) eol("return BinaryPackageFragmentRoot.all.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/set0.xmi",
IQueryEngine.PROPERTY_SUBTREE_DERIVEDALLOF, "true"
));
final int ipkgCount = (int) eol("return IPackageFragmentRoot.all.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/set0.xmi",
IQueryEngine.PROPERTY_SUBTREE_DERIVEDALLOF, "true"
));
assertTrue(String.format(
"IPackageFragmentRoot.all.size should be > than BinaryPackageFragmentRoot.all.size: checking %d > %d", ipkgCount, binpkgCount),
ipkgCount > binpkgCount);
List<String> pathsTouched = new ArrayList<>();
final Random rnd = new Random(42);
for (File f : FileUtils.listFiles(folderFragmented, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)) {
if (rnd.nextDouble() < 0.2) {
FileUtils.touch(f);
pathsTouched.add(f.getName());
}
}
indexer.requestImmediateSync();
scheduleAndWait(() -> {
final int ipkgCount2 = (int) eol("return IPackageFragmentRoot.all.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/set0.xmi",
IQueryEngine.PROPERTY_SUBTREE_DERIVEDALLOF, "true"
));
assertEquals(
"IPackageFragmentRoot.all.size should stay the same after touching " + pathsTouched,
ipkgCount, ipkgCount2);
return null;
});
}
@Test
public void subtreeTraversalScoping() throws Throwable {
prepareBoth();
// None of the external package fragment roots should be visible with traversal scoping on
final String eolQuery = "return IJavaProject.all.first.externalPackageFragmentRoots.size;";
final int fileClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_FILECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/*",
IQueryEngine.PROPERTY_ENABLE_TRAVERSAL_SCOPING, "true"));
assertEquals(0, fileClasses);
// Same should happen with the subtree context
final int subtreeClasses = (int) eol(eolQuery, ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi",
IQueryEngine.PROPERTY_ENABLE_TRAVERSAL_SCOPING, "true"));
assertEquals(0, subtreeClasses);
}
@Test
public void getFiles() throws Throwable {
prepareBoth();
final int fragmentedFilesCount = (int) eol("return Model.files.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_FILECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/*"));
final int subtreeFilesCount = (int) eol("return Model.files.size;", ctx(
IQueryEngine.PROPERTY_REPOSITORYCONTEXT, fragmentedRepoURI,
IQueryEngine.PROPERTY_SUBTREECONTEXT, "/IJavaProject_org.eclipse.jdt.apt.pluggable.core/IJavaProject_org.eclipse.jdt.apt.pluggable.core.xmi"
));
assertEquals(fragmentedFilesCount, subtreeFilesCount);
}
private static Map<String, Object> ctx(String... opts) {
Map<String, Object> ctx = new HashMap<>();
for (int i = 0; i + 1 < opts.length; i += 2) {
final String key = opts[i], value = opts[i + 1];
ctx.put(key, value);
}
return ctx;
}
/**
* Creates the fragmented version of set0, to help with testing.
*/
public static void main(String[] args) throws IOException {
new Fragmenter().run();
}
}