Assert execution order in validation tests with type cycles (#443)

The method execution order in type cycles depends on the JVM
implementation. This validation ensure our test setup produce the
required scenarios.
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java
index 59ec639..0ea8d1a 100644
--- a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java
@@ -30,12 +30,18 @@
 			// JDK-9042842
 			assertLine("baseclinit", ICounter.EMPTY);
 			assertLine("childdefault", ICounter.NOT_COVERED);
+			assertLogEvents("childclinit", "childstaticmethod");
 		} else {
 			assertLine("baseclinit", ICounter.FULLY_COVERED);
 			assertLine("childdefault", ICounter.FULLY_COVERED);
+
+			// The cycle causes a default method to be called before the static
+			// initializer of a interface:
+			assertLogEvents("baseclinit", "childdefaultmethod", "childclinit",
+					"childstaticmethod");
 		}
 		assertLine("childclinit", ICounter.FULLY_COVERED);
 		assertLine("childstatic", ICounter.FULLY_COVERED);
-	}
 
+	}
 }
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java
index 73ff334..f5a3499 100644
--- a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java
@@ -16,7 +16,7 @@
 	public interface Base {
 		static final Object BASE_CONST = new Child() {
 			{
-				Stubs.nop("base clinit"); // $line-baseclinit$
+				Stubs.logEvent("baseclinit"); // $line-baseclinit$
 			}
 		}.childDefaultMethod();
 
@@ -27,17 +27,17 @@
 	public interface Child extends Base {
 		static final Object CHILD_CONST = new Object() {
 			{
-				Stubs.nop("child clinit"); // $line-childclinit$
+				Stubs.logEvent("childclinit"); // $line-childclinit$
 			}
 		};
 
 		default Object childDefaultMethod() {
-			Stubs.nop("child default method"); // $line-childdefault$
+			Stubs.logEvent("childdefaultmethod"); // $line-childdefault$
 			return null;
 		}
 
 		static void childStaticMethod() {
-			Stubs.nop("child static method"); // $line-childstatic$
+			Stubs.logEvent("childstaticmethod"); // $line-childstatic$
 		}
 	}
 
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java
index cbf3c35..4c02d68 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java
@@ -29,6 +29,11 @@
 		assertLine("childinit", ICounter.FULLY_COVERED);
 		assertLine("childsomeMethod", ICounter.FULLY_COVERED);
 		assertLine("childclinit", ICounter.FULLY_COVERED);
+
+		// The cycle causes a constructor and instance method to be called
+		// before the static initializer of a class:
+		assertLogEvents("childinit", "childsomeMethod", "childclinit",
+				"childinit");
 	}
 
 }
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
index 67c4634..d6dc6a1 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
@@ -15,6 +15,8 @@
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
 
 import org.jacoco.core.analysis.Analyzer;
 import org.jacoco.core.analysis.CoverageBuilder;
@@ -26,6 +28,7 @@
 import org.jacoco.core.internal.analysis.CounterImpl;
 import org.jacoco.core.test.InstrumentingLoader;
 import org.jacoco.core.test.TargetLoader;
+import org.jacoco.core.test.validation.targets.Stubs;
 import org.junit.Before;
 
 /**
@@ -37,19 +40,21 @@
 	private static final String[] STATUS_NAME = new String[4];
 
 	{
-		STATUS_NAME[ICounter.EMPTY] = "NO_CODE";
+		STATUS_NAME[ICounter.EMPTY] = "EMPTY";
 		STATUS_NAME[ICounter.NOT_COVERED] = "NOT_COVERED";
 		STATUS_NAME[ICounter.FULLY_COVERED] = "FULLY_COVERED";
 		STATUS_NAME[ICounter.PARTLY_COVERED] = "PARTLY_COVERED";
 	}
 
-	protected final String srcFolder;
+	private final String srcFolder;
 
-	protected final Class<?> target;
+	private final Class<?> target;
 
-	protected ISourceFileCoverage sourceCoverage;
+	private ISourceFileCoverage sourceCoverage;
 
-	protected Source source;
+	private Source source;
+
+	private InstrumentingLoader loader;
 
 	protected ValidationTestBase(final String srcFolder, final Class<?> target) {
 		this.srcFolder = srcFolder;
@@ -68,7 +73,7 @@
 	}
 
 	private ExecutionDataStore execute() throws Exception {
-		InstrumentingLoader loader = new InstrumentingLoader(target);
+		loader = new InstrumentingLoader(target);
 		run(loader.loadClass(target.getName()));
 		return loader.collect();
 	}
@@ -128,4 +133,10 @@
 		assertLine(tag, missedBranches, coveredBranches);
 	}
 
+	protected void assertLogEvents(String... events) throws Exception {
+		final Method getter = Class.forName(Stubs.class.getName(), false,
+				loader).getMethod("getLogEvents");
+		assertEquals("Log events", Arrays.asList(events), getter.invoke(null));
+	}
+
 }
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java
index c524a21..cf49271 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java
@@ -24,15 +24,15 @@
 	public static class Child extends Base {
 
 		static {
-			Stubs.nop("child clinit"); // $line-childclinit$
+			Stubs.logEvent("childclinit"); // $line-childclinit$
 		}
 
 		public Child() {
-			Stubs.nop("child init"); // $line-childinit$
+			Stubs.logEvent("childinit"); // $line-childinit$
 		}
 
 		void someMethod() {
-			Stubs.nop("child someMethod"); // $line-childsomeMethod$
+			Stubs.logEvent("childsomeMethod"); // $line-childsomeMethod$
 		}
 
 	}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Stubs.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Stubs.java
index eae387b..c4cb5d4 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Stubs.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Stubs.java
@@ -11,6 +11,9 @@
  *******************************************************************************/
 package org.jacoco.core.test.validation.targets;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Collection of stub methods that are called from the coverage targets. *
  */
@@ -117,4 +120,24 @@
 	public static void noexec(Runnable task) {
 	}
 
+	/**
+	 * List of logged events. Using a static member here works as this class is
+	 * loaded in a new class loader for every test case.
+	 */
+	private static List<String> events = new ArrayList<String>();
+
+	/**
+	 * Records a event with the given id for later verification.
+	 */
+	public static void logEvent(String id) {
+		events.add(id);
+	}
+
+	/**
+	 * Returns a list of all recorded events in the sequence of recording.
+	 */
+	public static List<String> getLogEvents() {
+		return events;
+	}
+
 }