diff --git a/agent/bin/test_run b/agent/bin/test_run index 252bcc13..1af4b090 100755 --- a/agent/bin/test_run +++ b/agent/bin/test_run @@ -17,6 +17,8 @@ set -x ./test/petclinic/test if [[ $JAVA_VERSION == 17.* ]]; then ./test/petclinic-fw/test +else + ./test/junit4/test fi ./test/httpcore/test ./test/http_client/test diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/RecordMethod.java b/agent/src/main/java/com/appland/appmap/process/hooks/RecordMethod.java index 850708d9..b855fa92 100644 --- a/agent/src/main/java/com/appland/appmap/process/hooks/RecordMethod.java +++ b/agent/src/main/java/com/appland/appmap/process/hooks/RecordMethod.java @@ -15,7 +15,7 @@ public class RecordMethod { @ExcludeReceiver @HookCondition(RecordCondition.class) public static void record(Event event, Object[] args) { - RecordingSupport.startRecording(event, "record_process", "process"); + RecordingSupport.startRecording(event, new Recorder.Metadata("record_process", "process")); } @ArgumentArray diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/RecordingSupport.java b/agent/src/main/java/com/appland/appmap/process/hooks/RecordingSupport.java index 730927b0..cd6c2018 100644 --- a/agent/src/main/java/com/appland/appmap/process/hooks/RecordingSupport.java +++ b/agent/src/main/java/com/appland/appmap/process/hooks/RecordingSupport.java @@ -40,14 +40,13 @@ public TestDetails(Event event) { } } - public static void startRecording(Event event, String recorderName, String recorderType) { - startRecording(new TestDetails(event), recorderName, recorderType); + public static void startRecording(Event event, Recorder.Metadata metadata) { + startRecording(new TestDetails(event), metadata); } - public static void startRecording(TestDetails details, String recorderName, String recorderType) { + public static void startRecording(TestDetails details, Recorder.Metadata metadata) { logger.debug("Recording started for {}", canonicalName(details.definedClass, details.isStatic, details.methodId)); try { - Recorder.Metadata metadata = new Recorder.Metadata(recorderName, recorderType); final String feature = identifierToSentence(details.methodId); final String featureGroup = identifierToSentence(details.definedClass); metadata.scenarioName = String.format( diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit.java b/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit.java new file mode 100644 index 00000000..2a49c410 --- /dev/null +++ b/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit.java @@ -0,0 +1,81 @@ +package com.appland.appmap.process.hooks.test; + +import com.appland.appmap.output.v1.Event; +import com.appland.appmap.process.hooks.RecordingSupport; +import com.appland.appmap.record.Recorder; +import com.appland.appmap.transform.annotations.ArgumentArray; +import com.appland.appmap.transform.annotations.ExcludeReceiver; +import com.appland.appmap.transform.annotations.HookAnnotated; +import com.appland.appmap.transform.annotations.MethodEvent; + +public class JUnit { + private static final Recorder recorder = Recorder.getInstance(); + static final String JUNIT_NAME = "junit"; + + @ArgumentArray + @ExcludeReceiver + @HookAnnotated("org.junit.Test") + public static void junit(Event event, Object[] args) { + Recorder.Metadata metadata = new Recorder.Metadata(JUNIT_NAME, TestSupport.TEST_RECORDER_TYPE); + metadata.frameworks.add(new Recorder.Framework("JUnit", "4")); + RecordingSupport.startRecording(event, metadata); + } + + @ArgumentArray + @ExcludeReceiver + @HookAnnotated(value = "org.junit.Test", methodEvent = MethodEvent.METHOD_RETURN) + public static void junit(Event event, Object returnValue, Object[] args) { + RecordingSupport.stopRecording(event, true); + } + + @ArgumentArray + @HookAnnotated(value = "org.junit.Test", methodEvent = MethodEvent.METHOD_EXCEPTION) + public static void junit(Event event, Object self, Throwable exception, Object[] args) { + event.setException(exception); + recorder.add(event); + StackTraceElement ste = findErrorFrame(self, exception); + RecordingSupport.stopRecording(new RecordingSupport.TestDetails(event), false, exception.getMessage(), + ste.getLineNumber()); + } + + @ArgumentArray + @ExcludeReceiver + @HookAnnotated("org.junit.jupiter.api.Test") + public static void junit5Test(Event event, Object[] args) { + Recorder.Metadata metadata = new Recorder.Metadata(JUNIT_NAME, TestSupport.TEST_RECORDER_TYPE); + metadata.frameworks.add(new Recorder.Framework("JUnit", "5")); + RecordingSupport.startRecording(event, metadata); + } + + @ArgumentArray + @ExcludeReceiver + @HookAnnotated(value = "org.junit.jupiter.api.Test", methodEvent = MethodEvent.METHOD_RETURN) + public static void junit5Test(Event event, Object returnValue, Object[] args) { + RecordingSupport.stopRecording(event, true); + } + + @ArgumentArray + @HookAnnotated(value = "org.junit.jupiter.api.Test", methodEvent = MethodEvent.METHOD_EXCEPTION) + public static void junit5Test(Event event, Object self, Throwable exception, Object[] args) { + event.setException(exception); + recorder.add(event); + StackTraceElement errorFrame = findErrorFrame(self, exception); + RecordingSupport.stopRecording(new RecordingSupport.TestDetails(event), false, exception.getMessage(), + errorFrame.getLineNumber()); + } + + private static StackTraceElement findErrorFrame(Object self, Throwable exception) throws InternalError { + String selfClass = self.getClass().getName(); + StackTraceElement errorFrame = null; + for (StackTraceElement frame : exception.getStackTrace()) { + if (frame.getClassName().equals(selfClass)) { + errorFrame = frame; + break; + } + } + if (errorFrame == null) { + throw new InternalError("no stack frame matched test class"); + } + return errorFrame; + } +} diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit4.java b/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit4.java deleted file mode 100644 index 09226312..00000000 --- a/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit4.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.appland.appmap.process.hooks.test; - -import com.appland.appmap.output.v1.Event; -import com.appland.appmap.process.hooks.RecordingSupport; -import com.appland.appmap.record.Recorder; -import com.appland.appmap.transform.annotations.ArgumentArray; -import com.appland.appmap.transform.annotations.ExcludeReceiver; -import com.appland.appmap.transform.annotations.HookAnnotated; -import com.appland.appmap.transform.annotations.MethodEvent; - -public class JUnit4 { - private static final Recorder recorder = Recorder.getInstance(); - - @ArgumentArray - @ExcludeReceiver - @HookAnnotated("org.junit.Test") - public static void junit(Event event, Object[] args) { - RecordingSupport.startRecording(event, JUnit5.JUNIT_NAME, TestSupport.TEST_RECORDER_TYPE); - } - - @ArgumentArray - @ExcludeReceiver - @HookAnnotated(value = "org.junit.Test", methodEvent = MethodEvent.METHOD_RETURN) - public static void junit(Event event, Object returnValue, Object[] args) { - RecordingSupport.stopRecording(event, true); - } - - @ArgumentArray - @ExcludeReceiver - @HookAnnotated(value = "org.junit.Test", methodEvent = MethodEvent.METHOD_EXCEPTION) - public static void junit(Event event, Throwable exception, Object[] args) { - event.setException(exception); - recorder.add(event); - StackTraceElement ste = exception.getStackTrace()[0]; - RecordingSupport.stopRecording(new RecordingSupport.TestDetails(event), null, exception.getMessage(), - ste.getLineNumber()); - } - -} diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit5.java b/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit5.java deleted file mode 100644 index dda9d38c..00000000 --- a/agent/src/main/java/com/appland/appmap/process/hooks/test/JUnit5.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.appland.appmap.process.hooks.test; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.tinylog.TaggedLogger; - -import com.appland.appmap.config.AppMapConfig; -import com.appland.appmap.output.v1.Event; -import com.appland.appmap.process.hooks.RecordingSupport; -import com.appland.appmap.reflect.DynamicReflectiveType; -import com.appland.appmap.reflect.ReflectiveType; -import com.appland.appmap.transform.annotations.ArgumentArray; -import com.appland.appmap.transform.annotations.HookClass; -import com.appland.appmap.util.ClassUtil; - -public class JUnit5 { - public static final TaggedLogger logger = AppMapConfig.getLogger(null); - - static final String JUNIT_NAME = "junit"; - - @ArgumentArray - @HookClass(value = "org.junit.platform.launcher.core.LauncherConfig$Builder") - public static void build(Event event, Object receiver, Object[] args) { - // Gradle uses a separate classloader for the launcher. We need to use that - // classloader for reflection. - ClassLoader cl = receiver.getClass().getClassLoader(); - logger.trace("receiver: {}, receiver's class loader: {}", receiver.getClass().getName(), - cl); - - Builder builder = new Builder(receiver); - builder.addTestExecutionListener(TestExecutionListener.build(cl)); - } - - private static class Builder extends ReflectiveType { - private static String ADD_TEST_EXECUTION_LISTENERS = "addTestExecutionListeners"; - - private Object listenerArrayArg; - - public Builder(Object self) { - super(self); - - // addMethod fails here if we pass - // "[Lorg.junit.platform.launcher.TestExecutionListener;" as the type of - // the argument method. Whatever the current ClassLoader is can't handle - // an array of Objects, and throws a ClassNotFoundException. Use - // java.lang.reflect.Array to manually create an array of the appropriate - // type, then pass its class to addMethod. - listenerArrayArg = Array.newInstance( - ClassUtil.safeClassForName(getClassLoader(), "org.junit.platform.launcher.TestExecutionListener"), - 1); - addMethod(ADD_TEST_EXECUTION_LISTENERS, listenerArrayArg.getClass()); - } - - public void addTestExecutionListener(Object listener) { - Array.set(listenerArrayArg, 0, listener); - invokeVoidMethod(ADD_TEST_EXECUTION_LISTENERS, listenerArrayArg); - } - } - - private static class TestIdentifier extends ReflectiveType { - static class MethodSource extends ReflectiveType { - private static String GET_CLASS_NAME = "getClassName"; - private static String GET_METHOD_NAME = "getMethodName"; - private static String GET_METHOD_PARAMETER_TYPES = "getMethodParameterTypes"; - - public MethodSource(Object self) { - super(self); - addMethods(GET_CLASS_NAME, GET_METHOD_NAME, GET_METHOD_PARAMETER_TYPES); - } - - public String getClassName() { - return (String) invokeObjectMethod(GET_CLASS_NAME); - } - - public String getMethodName() { - return (String) invokeObjectMethod(GET_METHOD_NAME); - } - - public String getMethodParameterTypes() { - return (String) invokeObjectMethod(GET_METHOD_PARAMETER_TYPES); - } - } - - private static String GET_SOURCE = "getSource"; - private static String IS_TEST = "isTest"; - - public TestIdentifier(Object self) { - super(self); - addMethods(GET_SOURCE, IS_TEST); - } - - public Boolean isTest() { - return (Boolean) invokeObjectMethod(IS_TEST); - } - - public MethodSource getSource() { - Optional ret = (Optional) invokeObjectMethod(GET_SOURCE); - return ret.isPresent() ? new MethodSource(ret.get()) : null; - } - } - - private static class TestExecutionResult extends ReflectiveType { - private static String GET_STATUS = "getStatus"; - private static String GET_THROWABLE = "getThrowable"; - static Enum SUCCESSFUL; - - public TestExecutionResult(Object self) { - super(self); - addMethods(GET_STATUS, GET_THROWABLE); - SUCCESSFUL = ClassUtil.enumValueOf(getClassLoader(), "SUCCESSFUL", - "org.junit.platform.engine.TestExecutionResult$Status"); - } - - public Object getStatus() { - return invokeObjectMethod(GET_STATUS); - } - - public Throwable getThrowable() { - Optional ret = (Optional) invokeObjectMethod(GET_THROWABLE); - return ret.isPresent() ? (Throwable) ret.get() : null; - } - - } - - private static class TestExecutionListener implements InvocationHandler { - private TestExecutionListener() { - } - - public static Object build(ClassLoader cl) { - return DynamicReflectiveType.build(new TestExecutionListener(), cl, - "org.junit.platform.launcher.TestExecutionListener"); - } - - class JUnit5Details extends RecordingSupport.TestDetails { - JUnit5Details(TestIdentifier.MethodSource src) { - if (src == null) { - return; - } - - definedClass = src.getClassName(); - isStatic = false; // TODO: add support for static test methods - methodId = src.getMethodName(); - - ClassUtil.MethodLocation loc = ClassUtil.getMethodLocation(src.getClassName(), src.getMethodName(), - src.getMethodParameterTypes()); - if (loc == null) { - return; - } - path = loc.file; - lineNumber = String.valueOf(loc.line); - - } - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - if (methodName.equals("executionStarted")) { - TestIdentifier id = new TestIdentifier(args[0]); - logger.trace("executionStarted, id.isTest(): {}, id: {}", id.isTest(), id); - if (!id.isTest()) { - return null; - } - TestIdentifier.MethodSource src = id.getSource(); - RecordingSupport.startRecording(new JUnit5Details(src), JUNIT_NAME, TestSupport.TEST_RECORDER_TYPE); - } else if (methodName.equals("executionFinished")) { - TestIdentifier id = new TestIdentifier(args[0]); - if (!id.isTest()) { - return null; - } - TestExecutionResult result = new TestExecutionResult(args[1]); - logger.trace("executionFinished, result: {}", args[1]); - - TestIdentifier.MethodSource src = id.getSource(); - boolean succeeded = result.getStatus().equals(TestExecutionResult.SUCCESSFUL); - String failureMessage = null; - int failureLine = -1; - Throwable t = result.getThrowable(); - JUnit5Details details = new JUnit5Details(src); - logger.trace(t, "test failed at"); - if (t != null) { - failureMessage = t.getMessage(); - if (details.definedClass != null && details.methodId != null) { - for (StackTraceElement ste : t.getStackTrace()) { - logger.trace("ste: {}", ste); - if (ste.getClassName().equals(details.definedClass) && ste.getMethodName().equals(details.methodId)) { - failureLine = ste.getLineNumber(); - break; - } - } - } - } - - RecordingSupport.stopRecording(details, succeeded, failureMessage, failureLine); - - } else { - // Don't need any other notifications, default implementations in - // TestExecutionListener do nothing. - logger.trace("unhandled method {}", methodName); - } - - return null; - } - } -} \ No newline at end of file diff --git a/agent/src/main/java/com/appland/appmap/process/hooks/test/TestNG.java b/agent/src/main/java/com/appland/appmap/process/hooks/test/TestNG.java index 98929cb5..2084525f 100644 --- a/agent/src/main/java/com/appland/appmap/process/hooks/test/TestNG.java +++ b/agent/src/main/java/com/appland/appmap/process/hooks/test/TestNG.java @@ -2,6 +2,7 @@ import com.appland.appmap.output.v1.Event; import com.appland.appmap.process.hooks.RecordingSupport; +import com.appland.appmap.record.Recorder; import com.appland.appmap.transform.annotations.ArgumentArray; import com.appland.appmap.transform.annotations.ExcludeReceiver; import com.appland.appmap.transform.annotations.HookAnnotated; @@ -14,7 +15,7 @@ public class TestNG { @ExcludeReceiver @HookAnnotated("org.testng.annotations.Test") public static void testng(Event event, Object[] args) { - RecordingSupport.startRecording(event, TESTNG_NAME, TestSupport.TEST_RECORDER_TYPE); + RecordingSupport.startRecording(event, new Recorder.Metadata(TESTNG_NAME, TestSupport.TEST_RECORDER_TYPE)); } @ArgumentArray diff --git a/agent/src/main/java/com/appland/appmap/record/AppMapSerializer.java b/agent/src/main/java/com/appland/appmap/record/AppMapSerializer.java index c5d278e7..262f37fe 100644 --- a/agent/src/main/java/com/appland/appmap/record/AppMapSerializer.java +++ b/agent/src/main/java/com/appland/appmap/record/AppMapSerializer.java @@ -173,20 +173,24 @@ public void writeMetadata(Metadata metadata) throws IOException { this.json.writeValue(metadata.sourceLocation); } - this.json.writeKey("framework"); - this.json.startObject(); - { - if (metadata.framework != null) { - this.json.writeKey("name"); - this.json.writeValue(metadata.framework); - } - - if (metadata.frameworkVersion != null) { - this.json.writeKey("version"); - this.json.writeValue(metadata.frameworkVersion); + if (metadata.frameworks.size() > 0) { + this.json.writeKey("frameworks"); + this.json.startArray(); + for (Recorder.Framework framework : metadata.frameworks) { + this.json.startObject(); + if (framework.name != null) { + this.json.writeKey("name"); + this.json.writeValue(framework.name); + } + + if (framework.version != null) { + this.json.writeKey("version"); + this.json.writeValue(framework.version); + } + this.json.endObject(); } + this.json.endArray(); } - this.json.endObject(); if ( metadata.testSucceeded != null ) { this.json.writeKey("test_status"); diff --git a/agent/src/main/java/com/appland/appmap/record/Recorder.java b/agent/src/main/java/com/appland/appmap/record/Recorder.java index 4c8b04a9..174d085c 100644 --- a/agent/src/main/java/com/appland/appmap/record/Recorder.java +++ b/agent/src/main/java/com/appland/appmap/record/Recorder.java @@ -1,7 +1,9 @@ package com.appland.appmap.record; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; @@ -63,12 +65,20 @@ public static String sanitizeFilename(String filename) { * Data structure for reporting AppMap metadata. * These fields map to the 'metadata' section of the AppMap JSON. */ + public static class Framework { + public String name; + public String version; + + public Framework(String name, String version) { + this.name = name; + this.version = version; + } + } public static class Metadata { public String scenarioName; public String recorderName; public String recorderType; - public String framework; - public String frameworkVersion; + public List frameworks = new ArrayList<>(); public String recordedClassName; public String recordedMethodName; public String sourceLocation; diff --git a/agent/src/test/fixture/junit4/src/test/java/org/springframework/samples/petclinic/JUnit4Tests.java b/agent/src/test/fixture/junit4/src/test/java/org/springframework/samples/petclinic/JUnit4Tests.java new file mode 100644 index 00000000..a592297c --- /dev/null +++ b/agent/src/test/fixture/junit4/src/test/java/org/springframework/samples/petclinic/JUnit4Tests.java @@ -0,0 +1,21 @@ +package org.springframework.samples.petclinic; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class JUnit4Tests { + @Test + public void testItPasses() { + System.err.println("passing test"); + + assertTrue(true); + } + + @Test + public void testItFails() { + System.err.println("failing test"); + + assertTrue("false is not true", false); + } +} diff --git a/agent/src/test/fixture/src/test/java/org/springframework/samples/petclinic/Junit5Tests.java b/agent/src/test/fixture/petclinic/src/test/java/org/springframework/samples/petclinic/JUnit5Tests.java similarity index 93% rename from agent/src/test/fixture/src/test/java/org/springframework/samples/petclinic/Junit5Tests.java rename to agent/src/test/fixture/petclinic/src/test/java/org/springframework/samples/petclinic/JUnit5Tests.java index 4c9cdb42..fb7b62e4 100644 --- a/agent/src/test/fixture/src/test/java/org/springframework/samples/petclinic/Junit5Tests.java +++ b/agent/src/test/fixture/petclinic/src/test/java/org/springframework/samples/petclinic/JUnit5Tests.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; -public class Junit5Tests { +public class JUnit5Tests { @Test public void testItPasses() { System.err.println("passing test"); diff --git a/agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/system/NoContentController.java b/agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/system/NoContentController.java similarity index 100% rename from agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/system/NoContentController.java rename to agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/system/NoContentController.java diff --git a/agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/web/ExitController.java b/agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/web/ExitController.java similarity index 100% rename from agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/web/ExitController.java rename to agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/web/ExitController.java diff --git a/agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/web/ShowAvailable.java b/agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/web/ShowAvailable.java similarity index 100% rename from agent/src/test/fixture/src/main/java/org/springframework/samples/petclinic/web/ShowAvailable.java rename to agent/src/test/fixture/shared/src/main/java/org/springframework/samples/petclinic/web/ShowAvailable.java diff --git a/agent/test/jdbc/build.gradle b/agent/test/jdbc/build.gradle index cde62eb6..d403c7ff 100644 --- a/agent/test/jdbc/build.gradle +++ b/agent/test/jdbc/build.gradle @@ -32,6 +32,6 @@ test { "-javaagent:${appmapJar}", "-Dappmap.config.file=appmap.yml", // "-Dappmap.debug=true", - "-Dappmap.debug.file=../../build/log/jdbc-appmap.log" + // "-Dappmap.debug.file=../../build/log/jdbc-appmap.log" ] } diff --git a/agent/test/junit/.gitignore b/agent/test/junit/.gitignore deleted file mode 100644 index aa7edf6e..00000000 --- a/agent/test/junit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -spring-petclinic diff --git a/agent/test/junit/junit.bats b/agent/test/junit/junit.bats deleted file mode 100644 index 8f564365..00000000 --- a/agent/test/junit/junit.bats +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bats -# -# Runs smoke tests against a Spring sample application available here: -# https://github.com/spring-projects/spring-petclinic -# -# If running locally, keep in mind that this application will cache SQL results, -# likely causing subsequent test runs to fail. - -WS_URL=${WS_URL:-http://localhost:8080} - -load '../../build/bats/bats-support/load' -load '../../build/bats/bats-assert/load' -load '../helper' - -large_test_file="test/junit/spring-petclinic/target/appmap/org_springframework_samples_petclinic_owner_PetControllerTests_testProcessCreationFormHasErrors.appmap.json" - -@test "junit tests produce AppMaps" { - assert [ -d test/junit/spring-petclinic/target/appmap ] -} - -@test "framework is captured" { - output=$(cat "$large_test_file") - - assert_json_eq '.metadata.framework.name' 'junit' -} - -@test "defined_class is captured" { - output=$(cat "$large_test_file") - - assert_json_eq '.metadata.recording.defined_class' 'org.springframework.samples.petclinic.owner.PetControllerTests' -} - -# TODO: Verify source_location, test_status, exception -# To do that, the default location of the appmap.jar that's injected by maven needs to be changed. -# The default will look something like this: -# -javaagent:/Users/kgilpin/.m2/repository/com/appland/appmap-agent/1.0.4/appmap-agent-1.0.4.jar=com.appland.appmap.LoadJavaAppMapAgentMojo@53a20aab -# We need it to point to the freshly built appmap.jar. diff --git a/agent/test/junit/test b/agent/test/junit/test deleted file mode 100755 index 371fdc69..00000000 --- a/agent/test/junit/test +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -rsync -au --delete build/fixtures/spring-petclinic test/junit -cp test/petclinic/appmap.yml test/junit/spring-petclinic -cp test/junit/pom.xml test/junit/spring-petclinic/ - -project_dir=$(pwd) -cd test/junit/spring-petclinic -./mvnw test -cd "$project_dir" - -mkdir -p build/log - -run_bats() { - bats --tap test/junit/junit.bats > build/log/bats-junit.log 2>&1 -} -${@:-run_bats} -bats_ret=$? - -cat build/log/bats-junit.log -exit $bats_ret diff --git a/agent/test/junit4/.gitignore b/agent/test/junit4/.gitignore new file mode 100644 index 00000000..4781f345 --- /dev/null +++ b/agent/test/junit4/.gitignore @@ -0,0 +1 @@ +/spring-petclinic diff --git a/agent/test/junit4/appmap.yml b/agent/test/junit4/appmap.yml new file mode 100644 index 00000000..9d64fe5d --- /dev/null +++ b/agent/test/junit4/appmap.yml @@ -0,0 +1,3 @@ +name: Spring Pet Clinic +packages: +- path: org.springframework.samples.petclinic diff --git a/agent/test/junit4/junit.bats b/agent/test/junit4/junit.bats new file mode 100644 index 00000000..1f60df8e --- /dev/null +++ b/agent/test/junit4/junit.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats +# +# Runs smoke tests against a Spring sample application available here: +# https://github.com/spring-projects/spring-petclinic +# +# If running locally, keep in mind that this application will cache SQL results, +# likely causing subsequent test runs to fail. + + +load '../../build/bats/bats-support/load' +load '../../build/bats/bats-assert/load' +load '../helper' +load '../petclinic-shared/shared-setup.bash' + +setup_file() { + export AGENT_JAR="$(find_agent_jar)" + + export FIXTURE_DIR="test/junit4/spring-petclinic" + rm -rf "${FIXTURE_DIR}" + + git clone build/fixtures/spring-petclinic "${FIXTURE_DIR}" >&3 + _shared_setup + + cp appmap.yml "${FIXTURE_DIR}" + + cd "${FIXTURE_DIR}" + # Checkout the first commit before tests were upgraded top JUnit 5 + git checkout ce7c3f93 >&3 +} + +setup() { + rm -rf tmp/appmap +} + +run_tests() { + local test="${@}" + run ./mvnw -q -DtrimStackTrace=false \ + -Dcheckstyle.skip=true -Dspring-javaformat.skip=true \ + -DargLine="@{argLine} -javaagent:${AGENT_JAR}" \ + test -Dtest="$test" +} + +@test "framework is captured" { + run_tests "JUnit4Tests#testItPasses" + assert_success + output=$(< "tmp/appmap/junit/org_springframework_samples_petclinic_JUnit4Tests_testItPasses.appmap.json") + + assert_json_eq '.metadata.frameworks[0].name' 'JUnit' + assert_json_eq '.metadata.frameworks[0].version' '4' +} + +@test "defined_class is captured" { + run_tests "JUnit4Tests#testItPasses" + output=$(< "tmp/appmap/junit/org_springframework_samples_petclinic_JUnit4Tests_testItPasses.appmap.json") + + assert_json_eq '.metadata.recording.defined_class' 'org.springframework.samples.petclinic.JUnit4Tests' +} + +@test "test_status set for passing test" { + run_tests "JUnit4Tests#testItPasses" + assert_success + output=$(< "tmp/appmap/junit/org_springframework_samples_petclinic_JUnit4Tests_testItPasses.appmap.json") + + assert_json_eq '.metadata.test_status' "succeeded" +} + +@test "test_status set for failed test" { + run_tests "JUnit4Tests#testItFails" + assert_failure + + output=$(< "tmp/appmap/junit/org_springframework_samples_petclinic_JUnit4Tests_testItFails.appmap.json") + + assert_json_eq '.metadata.test_status' "failed" + assert_json_eq '.metadata.test_failure.message' 'false is not true' + assert_json_eq '.metadata.test_failure.location' 'org/springframework/samples/petclinic/JUnit4Tests.java:19' +} \ No newline at end of file diff --git a/agent/test/junit/pom.xml b/agent/test/junit4/pom.xml similarity index 100% rename from agent/test/junit/pom.xml rename to agent/test/junit4/pom.xml diff --git a/agent/test/junit4/test b/agent/test/junit4/test new file mode 100755 index 00000000..c7acc37d --- /dev/null +++ b/agent/test/junit4/test @@ -0,0 +1,3 @@ +#!/bin/bash + +bats --tap test/junit4 \ No newline at end of file diff --git a/agent/test/petclinic-shared/shared-setup.bash b/agent/test/petclinic-shared/shared-setup.bash index b808188a..11a2305a 100644 --- a/agent/test/petclinic-shared/shared-setup.bash +++ b/agent/test/petclinic-shared/shared-setup.bash @@ -1,3 +1,8 @@ _shared_setup() { - tar -C "./src/test/fixture" -c -f - .| tar -C "${FIXTURE_DIR}" -x -f - >&3 + local fixtureSrc="$(git rev-parse --show-toplevel)/agent/src/test/fixture" + tar -C "${fixtureSrc}/shared" -c -f - . | tar -C "${FIXTURE_DIR}" -x -f - + local testdir="$(basename ${BATS_TEST_DIRNAME})" + if [[ -d "${fixtureSrc}/${testdir}" ]]; then + tar -C "${fixtureSrc}/${testdir}" -c -f - . | tar -C "${FIXTURE_DIR}" -x -f - + fi } diff --git a/agent/test/petclinic/petclinic-tests.bats b/agent/test/petclinic/petclinic-tests.bats index 79009557..d233e0d5 100644 --- a/agent/test/petclinic/petclinic-tests.bats +++ b/agent/test/petclinic/petclinic-tests.bats @@ -16,13 +16,10 @@ setup_file() { export FIXTURE_DIR="build/fixtures/spring-petclinic" _shared_setup + export AGENT_JAR="$(find_agent_jar)" } setup() { - # bats doc says you'll get better error messages if you load helper scripts in - # setup. (Note that loading them # in setup_file doesn't work.) - export AGENT_JAR="$(find_agent_jar)" - cd build/fixtures/spring-petclinic rm -rf tmp/appmap } @@ -69,12 +66,15 @@ run_petclinic_test() { run ./mvnw \ -Dcheckstyle.skip=true -Dspring-javaformat.skip=true \ -DargLine="@{argLine} -javaagent:${AGENT_JAR} -Dappmap.config.file=../../../test/petclinic/appmap.yml" \ - test -Dtest="Junit5Tests#testItPasses" + test -Dtest="JUnit5Tests#testItPasses" assert_success - run cat ./tmp/appmap/junit/org_springframework_samples_petclinic_Junit5Tests_testItPasses.appmap.json + run cat ./tmp/appmap/junit/org_springframework_samples_petclinic_JUnit5Tests_testItPasses.appmap.json assert_success + assert_json_eq '.metadata.frameworks[0].name' 'JUnit' + assert_json_eq '.metadata.frameworks[0].version' '5' + assert_json_eq '.metadata.test_status' 'succeeded' } @@ -82,14 +82,14 @@ run_petclinic_test() { run ./mvnw \ -Dcheckstyle.skip=true -Dspring-javaformat.skip=true \ -DargLine="@{argLine} -javaagent:${AGENT_JAR} -Dappmap.config.file=../../../test/petclinic/appmap.yml" \ - test -Dtest="Junit5Tests#testItFails" + test -Dtest="JUnit5Tests#testItFails" assert_failure - run cat ./tmp/appmap/junit/org_springframework_samples_petclinic_Junit5Tests_testItFails.appmap.json + run cat ./tmp/appmap/junit/org_springframework_samples_petclinic_JUnit5Tests_testItFails.appmap.json assert_success assert_json_eq '.metadata.test_status' 'failed' assert_json_eq '.metadata.test_failure.message' 'expected: but was: ' - assert_json_eq '.metadata.test_failure.location' 'org/springframework/samples/petclinic/Junit5Tests.java:19' + assert_json_eq '.metadata.test_failure.location' 'org/springframework/samples/petclinic/JUnit5Tests.java:19' }