SyncValidationListener: collect errors for JUnit tests
diff --git a/plugins/org.hawk.graph.syncValidationListener/src/org/hawk/graph/syncValidationListener/SyncValidationListener.java b/plugins/org.hawk.graph.syncValidationListener/src/org/hawk/graph/syncValidationListener/SyncValidationListener.java
index 5173d70..254421b 100644
--- a/plugins/org.hawk.graph.syncValidationListener/src/org/hawk/graph/syncValidationListener/SyncValidationListener.java
+++ b/plugins/org.hawk.graph.syncValidationListener/src/org/hawk/graph/syncValidationListener/SyncValidationListener.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2011-2016 The University of York.

+ * Copyright (c) 2011-2019 The University of York, 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

@@ -13,6 +13,7 @@
  *

  * Contributors:

  *     Konstantinos Barmpis - initial API and implementation

+ *     Antonio Garcia-Dominguez - collect errors in list instead of printing them

  ******************************************************************************/

 package org.hawk.graph.syncValidationListener;

 

@@ -20,10 +21,12 @@
 import java.net.URI;

 import java.net.URISyntaxException;

 import java.net.URLDecoder;

+import java.util.ArrayList;

 import java.util.Arrays;

 import java.util.HashMap;

 import java.util.HashSet;

 import java.util.Iterator;

+import java.util.List;

 import java.util.Map;

 import java.util.Map.Entry;

 import java.util.Set;

@@ -50,8 +53,24 @@
 

 public class SyncValidationListener implements IGraphChangeListener {

 

+	public class ValidationError {

+		private final String message;

+

+		public ValidationError(String message) {

+			this.message = message;

+		}

+

+		public String getMessage() {

+			return message;

+		}

+	}

+

 	private ModelIndexerImpl hawk;

-	private int removedProxies, totalErrors, deleted, malformed, singletonCount, totalGraphSize, totalResourceSizes;

+

+	// stats

+	private List<ValidationError> errors = new ArrayList<>();

+	private int removedProxies, deleted, malformed, singletonCount, totalGraphSize, totalResourceSizes;

+

 	private Set<String> changed = new HashSet<>();

 

 	private IGraphNodeIndex singletonIndex;

@@ -95,8 +114,12 @@
 		changed.clear();

 	}

 

+	public List<ValidationError> getErrors() {

+		return errors;

+	}

+

 	public int getTotalErrors() {

-		return totalErrors;

+		return errors.size();

 	}

 

 	private void validateChanges() throws URISyntaxException {

@@ -113,8 +136,8 @@
 

 		System.err.println("validating changes...");

 

+		errors.clear();

 		removedProxies = 0;

-		totalErrors = 0;

 		malformed = 0;

 		singletonCount = 0;

 		totalResourceSizes = 0;

@@ -130,19 +153,20 @@
 		}

 

 		System.err.println("changed resource size: " + totalResourceSizes);

-

 		System.err.println("relevant graph size: "

 				+ totalGraphSize

 				+ (singletonCount > 0 ? (" + singleton count: " + singletonCount) : ""));

 

 		if (totalGraphSize + singletonCount != totalResourceSizes) {

-			totalErrors++;

+			errors.add(new ValidationError(

+				String.format("Mismatched resource size:  %d + %d != %d", totalGraphSize, singletonCount, totalResourceSizes)

+			));

 		}

 

 		System.err.println("validated changes... "

-				+ (totalErrors == 0 ? "true"

-						: ((totalErrors == malformed) + " (with "

-								+ totalErrors + " total and "

+				+ (errors.isEmpty() ? "true"

+						: ((errors.size() == malformed) + " (with "

+								+ errors.size() + " total and "

 								+ malformed + " malformed errors)"))

 				+ (removedProxies == 0 ? "" : " [" + removedProxies

 						+ "] unresolved hawk proxies matched"));

@@ -174,33 +198,36 @@
 				file = repository + GraphModelUpdater.FILEINDEX_REPO_SEPARATOR + c.getPath();

 				filenode = graph.getFileIndex().get("id", file).getSingle();

 			} catch (Exception ee) {

-				System.err.println("expected file " + file

-						+ " but it did not exist (maybe metamodel not registered, if so expect +1 errors)");

-				totalErrors++;

+				errors.add(new ValidationError(

+					String.format(

+						"Expected file %s but it did not exist "

+						+ "(maybe metamodel not registered, if so expect +1 errors)",

+						file)

+				));

 				return;

 			}

 

 			// cache model elements in current resource

-			Map<String, IHawkObject> eobjectCache = new HashMap<>();

+			Map<String, IHawkObject> eObjectCache = new HashMap<>();

 			// cache of malformed object identifiers (to ignore references)

 			Set<String> malformedObjectCache = new HashSet<>();

-			cacheModelElements(c, r, eobjectCache, malformedObjectCache);

+			cacheModelElements(c, r, eObjectCache, malformedObjectCache);

 

 			// go through all nodes in graph from the file the resource is in

 			for (IGraphEdge instanceEdge : filenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_FILE)) {

 				final IGraphNode instance = instanceEdge.getStartNode();

 				totalGraphSize++;

 

-				final IHawkObject eobject = eobjectCache.get(instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY));

+				final IHawkObject eobject = eObjectCache.get(instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY));

 

 				// if a node cannot be found in the model cache

 				if (eobject == null) {

-					System.err.println("error in validating: graph contains node with identifier:"

-							+ instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY) + " but resource does not!");

 					// this triggers when a malformed model has 2 identical identifiers

-					totalErrors++;

+					errors.add(new ValidationError(

+						String.format("Graph contains node with identifier: %s but resource does not!", instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY))

+					));

 				} else {

-					eobjectCache.remove(instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY));

+					eObjectCache.remove(instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY));

 

 					if (!malformedObjectCache.contains(eobject.getUri())) {

 						compareAttributes(instance, eobject);

@@ -210,10 +237,10 @@
 			}

 

 			// if there are model elements not found in nodes

-			if (eobjectCache.size() > 0) {

-				System.err.println("error in validating: the following objects were not found in the graph:");

-				System.err.println(eobjectCache.keySet());

-				totalErrors++;

+			if (eObjectCache.size() > 0) {

+				errors.add(new ValidationError(

+					String.format("The following objects were not found in the graph:\n%s", eObjectCache.keySet())

+				));

 			}

 

 			t.success();

@@ -279,21 +306,25 @@
 			filterFragmentBasedReferences(noderefvaluesclone, modelrefvaluesclone);

 

 			if (noderefvaluesclone.size() > 0) {

-				System.err.println("error in validating: reference " + modelRefName + " of node: "

-						+ instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY) + "\nlocated: "

-						+ instance.getOutgoingWithType(ModelElementNode.EDGE_LABEL_FILE).iterator().next().getEndNode()

-								.getProperty(IModelIndexer.IDENTIFIER_PROPERTY));

-				System.err.println(noderefvaluesclone);

-				System.err.println("the above ids were found in the graph but not the model");

-				totalErrors++;

+				final IGraphNode fileNode = instance.getOutgoingWithType(ModelElementNode.EDGE_LABEL_FILE).iterator().next().getEndNode();

+				errors.add(new ValidationError(

+					String.format(

+						"Reference %s of node: %s\n"

+						+ "Located: %s\n%s\n"

+						+ "The above IDs were found in the graph but not the model",

+						modelRefName, instance.getProperty(IModelIndexer.IDENTIFIER_PROPERTY),

+						fileNode, noderefvaluesclone)

+				));

 			}

 

 			if (modelrefvaluesclone.size() > 0) {

-				System.err.println("error in validating: reference " + modelRefName + " of model element: "

-						+ eobject.getUriFragment() + "\nlocated: " + eobject.getUri());

-				System.err.println(modelrefvaluesclone);

-				System.err.println("the above ids were found in the model but not the graph");

-				totalErrors++;

+				errors.add(new ValidationError(

+					String.format("Reference %s of model element: %s\n"

+							+ "Located: %s\n%s\n"

+							+ "The above IDs were found in the model but not the graph",

+							modelRefName, eobject.getUriFragment(),

+							eobject.getUri(), modelrefvaluesclone)

+				));

 			}

 

 			nodereferences.remove(modelRefName);

@@ -301,10 +332,10 @@
 		}

 

 		if (nodereferences.size() > 0) {

-			System.err.println("error in validating: references " + nodereferences.keySet()

-					+ " had targets in the graph but not in the model: ");

-			System.err.println(nodereferences);

-			totalErrors++;

+			errors.add(new ValidationError(String.format(

+				"References %s had targets in the graph but not in the model:\n%s",

+				nodereferences.keySet(), nodereferences)

+			));

 		}

 	}

 

@@ -409,29 +440,28 @@
 				Object attr = modelAttributes.get(propertykey);

 

 				if (!flattenedStringEquals(dbattr, attr)) {

-					totalErrors++;

-					System.err.println("error in validating, attribute: " + propertykey + " has values:");

-					final String cla1 = dbattr != null ? dbattr.getClass().toString() : "null attr";

-					System.err.println(String.format("database:\t%s JAVATYPE: %s IN NODE: %s WITH ID: %s",

-						(dbattr instanceof Object[] ? (Arrays.asList((Object[]) dbattr)) : dbattr),

-						cla1, node.getId(), node.getProperty(IModelIndexer.IDENTIFIER_PROPERTY)));

+					final String dbJavaType = dbattr != null ? dbattr.getClass().toString() : "null attr";

+					final Object dbValue = dbattr instanceof Object[] ? (Arrays.asList((Object[]) dbattr)) : dbattr;

+					final String modelJavaType = attr != null ? attr.getClass().toString() : "null attr";

+					final Object modelValue = attr instanceof Object[] ? (Arrays.asList((Object[]) attr)) : attr;

 

-					String cla2 = attr != null ? attr.getClass().toString() : "null attr";

-					System.err.println(String.format("model:\t\t%s JAVATYPE: %s IN ELEMENT WITH ID %s",

-						(attr instanceof Object[] ? (Arrays.asList((Object[]) attr)) : attr),

-						cla2, modelElement.getUriFragment()));

+					errors.add(new ValidationError(String.format(

+						"Attribute %s has mismatched values:\n"

+							+ " * database:\t%s JAVATYPE: %s IN NODE: %s WITH ID: %s\n"

+							+ " * model:\t\t%s JAVATYPE: %s IN ELEMENT WITH ID %s",

+						propertykey,

+						dbValue, dbJavaType, node.getId(), node.getProperty(IModelIndexer.IDENTIFIER_PROPERTY),

+						modelValue, modelJavaType, modelElement.getUriFragment()

+					)));

 				}

 

 				modelAttributes.remove(propertykey);

 			}

 		}

 		if (modelAttributes.size() > 0) {

-			System.err.println(String.format(

-				"error in validating, the following attributes were "

-				+ "not found in the graph node %s: %s",

-				node.getId(), modelAttributes.keySet()));

-

-			totalErrors++;

+			errors.add(new ValidationError(String.format(

+				"The following attributes were not found in the graph node %s: %s", node.getId(), modelAttributes.keySet()

+			)));

 		}

 	}

 

diff --git a/tests/org.hawk.integration.tests/src/org/hawk/integration/tests/emf/SubtreeContextTest.java b/tests/org.hawk.integration.tests/src/org/hawk/integration/tests/emf/SubtreeContextTest.java
index 339da71..8314342 100644
--- a/tests/org.hawk.integration.tests/src/org/hawk/integration/tests/emf/SubtreeContextTest.java
+++ b/tests/org.hawk.integration.tests/src/org/hawk/integration/tests/emf/SubtreeContextTest.java
@@ -18,6 +18,7 @@
 

 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;

@@ -27,6 +28,7 @@
 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;

@@ -43,6 +45,7 @@
 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;

@@ -176,15 +179,25 @@
 	protected void prepareFragmented() throws Exception, Throwable {

 		requestFolderIndex(folderFragmented);

 		scheduleAndWait(() -> {

-			assertEquals(0, syncValidation.getListener().getTotalErrors());

+			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(() -> {

-			assertEquals(0, syncValidation.getListener().getTotalErrors());

+			assertNoValidationErrors("There should be no validation errors on the original model");

 			return null;

 		});

 	}