From 1dc418ae84b8de91b3a4a7d9615558c7ae8f4a0d Mon Sep 17 00:00:00 2001 From: chrisly42 Date: Wed, 14 Jul 2021 17:36:15 +0200 Subject: [PATCH] Added JUnit5 framework. --- .travis.yml | 10 + .../jupiter/LightCodeInsightExtension.java | 141 +++++++++++ .../platon42/intellij/jupiter/MyFixture.java | 11 + .../platon42/intellij/jupiter/MyParser.java | 14 ++ .../platon42/intellij/jupiter/MyTestCase.java | 11 + .../intellij/jupiter/ParserResultFile.java | 10 + .../jupiter/ParsingTestExtension.java | 232 ++++++++++++++++++ .../intellij/jupiter/TestDataPath.java | 10 + .../intellij/jupiter/TestDataSubPath.java | 10 + 9 files changed, 449 insertions(+) create mode 100644 .travis.yml create mode 100644 src/test/java/de/platon42/intellij/jupiter/LightCodeInsightExtension.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/MyFixture.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/MyParser.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/MyTestCase.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/ParserResultFile.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/ParsingTestExtension.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/TestDataPath.java create mode 100644 src/test/java/de/platon42/intellij/jupiter/TestDataSubPath.java diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8f7e0c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: java +jdk: + - openjdk11 + +before_script: + - chmod +x gradlew + - chmod +x gradle/wrapper/gradle-wrapper.jar + +script: + - ./gradlew clean test jacocoTestReport coveralls \ No newline at end of file diff --git a/src/test/java/de/platon42/intellij/jupiter/LightCodeInsightExtension.java b/src/test/java/de/platon42/intellij/jupiter/LightCodeInsightExtension.java new file mode 100644 index 0000000..2196270 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/LightCodeInsightExtension.java @@ -0,0 +1,141 @@ +package de.platon42.intellij.jupiter; + + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import com.intellij.testFramework.EdtTestUtilKt; +import com.intellij.testFramework.TestLoggerFactory; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import com.intellij.testFramework.fixtures.CodeInsightTestFixture; +import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.nio.file.Paths; +import java.util.logging.Logger; + +public class LightCodeInsightExtension implements ParameterResolver, AfterTestExecutionCallback, InvocationInterceptor { + + private static final Logger LOG = Logger.getLogger(LightCodeInsightExtension.class.getName()); + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + Parameter parameter = parameterContext.getParameter(); + return parameter.isAnnotationPresent(MyFixture.class) + || parameter.isAnnotationPresent(MyTestCase.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + LightCodeInsightFixtureTestCaseWrapper testCase = getWrapper(extensionContext); + Parameter parameter = parameterContext.getParameter(); + if (parameter.isAnnotationPresent(MyFixture.class)) return testCase.getMyFixture(); + else if (parameter.isAnnotationPresent(MyTestCase.class)) return testCase; + return null; + } + + private LightCodeInsightFixtureTestCaseWrapper getWrapper(ExtensionContext extensionContext) { + Store store = getStore(extensionContext); + return (LightCodeInsightFixtureTestCaseWrapper) store.getOrComputeIfAbsent("testCase", + key -> { + LightCodeInsightFixtureTestCaseWrapper wrapper = new LightCodeInsightFixtureTestCaseWrapper(extensionContext); + try { + wrapper.setUp(); + } catch (Exception e) { + LOG.severe("Exception during setUp(): " + e); + throw new IllegalStateException("Exception during setUp()", e); + } + return wrapper; + }); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + Store store = getStore(context); + LightCodeInsightFixtureTestCaseWrapper testCase = (LightCodeInsightFixtureTestCaseWrapper) store.get("testCase"); + if (testCase != null) testCase.tearDown(); + } + + private static Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(LightCodeInsightExtension.class, context.getRequiredTestMethod())); + } + + @Override + public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + Throwable[] throwables = new Throwable[1]; + + Runnable runnable = () -> { + try { + TestLoggerFactory.onTestStarted(); + invocation.proceed(); + TestLoggerFactory.onTestFinished(true); + } catch (Throwable e) { + TestLoggerFactory.onTestFinished(false); + throwables[0] = e; + } + }; + + invokeTestRunnable(runnable); + + if (throwables[0] != null) throw throwables[0]; + } + + private static void invokeTestRunnable(@NotNull Runnable runnable) { + IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current(); + if (policy != null && !policy.runInDispatchThread()) runnable.run(); + else EdtTestUtilKt.runInEdtAndWait(() -> { + runnable.run(); + return null; + }); + } + + private static class LightCodeInsightFixtureTestCaseWrapper extends BasePlatformTestCase { + private final ExtensionContext extensionContext; + + private LightCodeInsightFixtureTestCaseWrapper(ExtensionContext extensionContext) { + this.extensionContext = extensionContext; + } + + @Override + public void setUp() throws Exception { + super.setUp(); + Store store = getStore(extensionContext); + store.put("disposable", Disposer.newDisposable("LightCodeInsightFixtureTestCaseWrapper")); + } + + @Override + public void tearDown() throws Exception { + Store store = getStore(extensionContext); + Disposable disposable = (Disposable) store.get("disposable"); + if (myFixture != null && disposable != null) { + Disposer.dispose(disposable); + store.remove("disposable"); + } + super.tearDown(); + } + + @Override + protected String getTestDataPath() { + TestDataPath testDataPath = getMethodOrClassAnnotation(TestDataPath.class); + if (testDataPath == null) return super.getTestDataPath(); + TestDataSubPath testDataSubPath = getMethodOrClassAnnotation(TestDataSubPath.class); + if (testDataSubPath == null) return testDataPath.value(); + return Paths.get(testDataPath.value(), testDataSubPath.value()).toString(); + } + + public CodeInsightTestFixture getMyFixture() { + return myFixture; + } + + private T getMethodOrClassAnnotation(Class clazz) { + T annotation = extensionContext.getRequiredTestMethod().getAnnotation(clazz); + if (annotation == null) annotation = extensionContext.getRequiredTestClass().getAnnotation(clazz); + return annotation; + } + } +} diff --git a/src/test/java/de/platon42/intellij/jupiter/MyFixture.java b/src/test/java/de/platon42/intellij/jupiter/MyFixture.java new file mode 100644 index 0000000..593efbb --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/MyFixture.java @@ -0,0 +1,11 @@ +package de.platon42.intellij.jupiter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyFixture { +} diff --git a/src/test/java/de/platon42/intellij/jupiter/MyParser.java b/src/test/java/de/platon42/intellij/jupiter/MyParser.java new file mode 100644 index 0000000..ac602a3 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/MyParser.java @@ -0,0 +1,14 @@ +package de.platon42.intellij.jupiter; + +import com.intellij.lang.ParserDefinition; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface MyParser { + Class value(); + + String extension(); +} diff --git a/src/test/java/de/platon42/intellij/jupiter/MyTestCase.java b/src/test/java/de/platon42/intellij/jupiter/MyTestCase.java new file mode 100644 index 0000000..65d46a1 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/MyTestCase.java @@ -0,0 +1,11 @@ +package de.platon42.intellij.jupiter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyTestCase { +} diff --git a/src/test/java/de/platon42/intellij/jupiter/ParserResultFile.java b/src/test/java/de/platon42/intellij/jupiter/ParserResultFile.java new file mode 100644 index 0000000..8ab0765 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/ParserResultFile.java @@ -0,0 +1,10 @@ +package de.platon42.intellij.jupiter; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ParserResultFile { + String value(); +} diff --git a/src/test/java/de/platon42/intellij/jupiter/ParsingTestExtension.java b/src/test/java/de/platon42/intellij/jupiter/ParsingTestExtension.java new file mode 100644 index 0000000..36d0ad9 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/ParsingTestExtension.java @@ -0,0 +1,232 @@ +package de.platon42.intellij.jupiter; + + +import com.intellij.lang.ParserDefinition; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.EdtTestUtilKt; +import com.intellij.testFramework.ParsingTestCase; +import com.intellij.testFramework.TestLoggerFactory; +import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.nio.file.Paths; +import java.util.logging.Logger; + +public class ParsingTestExtension implements ParameterResolver, AfterTestExecutionCallback, InvocationInterceptor { + + private static final Logger LOG = Logger.getLogger(ParsingTestExtension.class.getName()); + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + Parameter parameter = parameterContext.getParameter(); + return parameter.isAnnotationPresent(MyTestCase.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + ParsingTestCaseWrapper testCase = getWrapper(extensionContext); + Parameter parameter = parameterContext.getParameter(); + if (parameter.isAnnotationPresent(MyTestCase.class)) { + return testCase; + } + return null; + } + + private ParsingTestCaseWrapper getWrapper(ExtensionContext extensionContext) { + Store store = getStore(extensionContext); + return (ParsingTestCaseWrapper) store.getOrComputeIfAbsent("testCase", + key -> { + MyParser myParser = extensionContext.getRequiredTestClass().getAnnotation(MyParser.class); + ParserDefinition parserDefinition; + try { + parserDefinition = myParser.value().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { + throw new RuntimeException("Could not instantiate ParserDefinition!", ex); + } + ParsingTestCaseWrapper wrapper = new ParsingTestCaseWrapper(extensionContext, myParser.extension(), parserDefinition); + try { + wrapper.setUp(); + } catch (Exception e) { + LOG.severe("Exception during setUp(): " + e); + throw new IllegalStateException("Exception during setUp()", e); + } + return wrapper; + }); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + Store store = getStore(context); + ParsingTestCaseWrapper testCase = (ParsingTestCaseWrapper) store.get("testCase"); + if (testCase != null) { + testCase.tearDown(); + } + } + + private static Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(ParsingTestExtension.class, context.getRequiredTestMethod())); + } + + @Override + public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + Throwable[] throwables = new Throwable[1]; + + Runnable runnable = () -> { + try { + TestLoggerFactory.onTestStarted(); + invocation.proceed(); + TestLoggerFactory.onTestFinished(true); + } catch (Throwable e) { + TestLoggerFactory.onTestFinished(false); + throwables[0] = e; + } + }; + + invokeTestRunnable(runnable); + + if (throwables[0] != null) { + throw throwables[0]; + } + } + + private static void invokeTestRunnable(@NotNull Runnable runnable) { + IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current(); + if (policy != null && !policy.runInDispatchThread()) { + runnable.run(); + } else { + EdtTestUtilKt.runInEdtAndWait(() -> { + runnable.run(); + return null; + }); + } + } + + public interface IParsingTestCase { + void ensureNoErrorElements(); + + void doTest(boolean checkResult); + + void doTest(boolean checkResult, boolean ensureNoErrorElements); + + PsiFile parseFile(String name, String text); + + void doCodeTest(@NotNull String code) throws IOException; + + PsiFile getFile(); + + void ensureCorrectReparse(); + } + + private static class ParsingTestCaseWrapper extends ParsingTestCase implements IParsingTestCase { + private ExtensionContext extensionContext; + private static ExtensionContext extensionContextHack; + + private ParsingTestCaseWrapper(ExtensionContext extensionContext, String extension, ParserDefinition parserDefinition) { + super(passthroughInitHack(extensionContext), extension, parserDefinition); + this.extensionContext = extensionContext; + } + + private static String passthroughInitHack(ExtensionContext extensionContext) { + extensionContextHack = extensionContext; + return ""; + } + + @Override + public void setUp() throws Exception { + super.setUp(); + Store store = getStore(extensionContext); + store.put("disposable", Disposer.newDisposable("ParsingTestCaseWrapper")); + } + + @Override + public void tearDown() throws Exception { + Store store = getStore(extensionContext); + Disposable disposable = (Disposable) store.get("disposable"); + if (disposable != null) { + Disposer.dispose(disposable); + store.remove("disposable"); + } + super.tearDown(); // Clears fields! + } + + @Override + public String getName() { + String testname = extensionContext.getDisplayName().replace(" ", "_"); + ParserResultFile parserResultFile = extensionContext.getRequiredTestMethod().getAnnotation(ParserResultFile.class); + if (parserResultFile != null) { + testname = parserResultFile.value(); + } + return testname; + } + + @Override + protected String getTestDataPath() { + if (extensionContext == null) // Method is called by super-constructor, damnit! + { + extensionContext = extensionContextHack; + } + TestDataPath testDataPath = getMethodOrClassAnnotation(TestDataPath.class); + if (testDataPath == null) { + return super.getTestDataPath(); + } + TestDataSubPath testDataSubPath = getMethodOrClassAnnotation(TestDataSubPath.class); + if (testDataSubPath == null) { + return testDataPath.value(); + } + return Paths.get(testDataPath.value(), testDataSubPath.value()).toString(); + } + + private T getMethodOrClassAnnotation(Class clazz) { + T annotation = extensionContext.getRequiredTestMethod().getAnnotation(clazz); + if (annotation == null) { + annotation = extensionContext.getRequiredTestClass().getAnnotation(clazz); + } + return annotation; + } + + @Override + public void ensureNoErrorElements() { + super.ensureNoErrorElements(); + } + + @Override + public void doTest(boolean checkResult) { + super.doTest(checkResult); + } + + @Override + public void doTest(boolean checkResult, boolean ensureNoErrorElements) { + super.doTest(checkResult, ensureNoErrorElements); + } + + @Override + public PsiFile parseFile(String name, String text) { + return super.parseFile(name, text); + } + + @Override + public void doCodeTest(@NotNull String code) throws IOException { + super.doCodeTest(code); + } + + @Override + public PsiFile getFile() { + return myFile; + } + + @Override + public void ensureCorrectReparse() { + ensureCorrectReparse(myFile); + } + } +} diff --git a/src/test/java/de/platon42/intellij/jupiter/TestDataPath.java b/src/test/java/de/platon42/intellij/jupiter/TestDataPath.java new file mode 100644 index 0000000..3b07a79 --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/TestDataPath.java @@ -0,0 +1,10 @@ +package de.platon42.intellij.jupiter; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TestDataPath { + String value(); +} diff --git a/src/test/java/de/platon42/intellij/jupiter/TestDataSubPath.java b/src/test/java/de/platon42/intellij/jupiter/TestDataSubPath.java new file mode 100644 index 0000000..2c7d3fc --- /dev/null +++ b/src/test/java/de/platon42/intellij/jupiter/TestDataSubPath.java @@ -0,0 +1,10 @@ +package de.platon42.intellij.jupiter; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TestDataSubPath { + String value(); +}