001package org.hl7.fhir.utilities.tests.execution.junit5; 002 003import lombok.Getter; 004import lombok.Setter; 005import org.hl7.fhir.utilities.tests.execution.CliTestSummary; 006import org.hl7.fhir.utilities.tests.execution.ModuleTestExecutor; 007import org.junit.platform.engine.TestExecutionResult; 008import org.junit.platform.launcher.*; 009import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 010import org.junit.platform.launcher.core.LauncherFactory; 011import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 012import org.junit.platform.launcher.listeners.TestExecutionSummary; 013 014import java.io.PrintStream; 015import java.util.Arrays; 016import java.util.Collections; 017import java.util.List; 018import java.util.stream.Collectors; 019 020import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; 021import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 022 023/** 024 * This class allows JUnit 5 tests to be run in Java runtime. 025 * 026 * JUnit 4 tests ARE NOT SUPPORTED. 027 * 028 * This mimics for the most part the output of the Maven Surefire plugin, with 029 * additional summaries and unique IDs for diagnosing failing tests. 030 */ 031public class JUnit5ModuleTestExecutor extends ModuleTestExecutor { 032 033 034 035 private static final String SUMMARY_TEMPLATE = "Tests run: %d, Failures: %d, Errors: %d, Skipped: %d"; 036 037 private static final String MODULE_FINISHED_TEMPLATE = "%s module tests finished."; 038 public static final String DEFAULT_CLASSNAME_FILTER = org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; 039 040 041 @Getter 042 private final String moduleName; 043 044 @Getter 045 private final List<String> packageNames; 046 047 public JUnit5ModuleTestExecutor(String moduleName, List<String> packageNames) { 048 this.moduleName = moduleName; 049 this.packageNames = Collections.unmodifiableList(packageNames); 050 } 051 052 private List<org.junit.platform.engine.discovery.PackageSelector> getPackageSelectors() { 053 final List<String> packageNames = getPackageNames(); 054 return packageNames.stream().map(it -> selectPackage(it)).collect(Collectors.toList()); 055 } 056 057 class ModuleTestExecutionListener extends SummaryGeneratingListener { 058 059 @Setter 060 private PrintStream out; 061 062 @Override 063 public void executionStarted(TestIdentifier testIdentifier) { 064 ModuleTestExecutor.printTestStarted(out, testIdentifier.getDisplayName()); 065 super.executionStarted(testIdentifier); 066 } 067 068 @Override 069 public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { 070 // 071 if (testExecutionResult.getStatus().equals(TestExecutionResult.Status.FAILED)) { 072 ModuleTestExecutor.printTestFailed(out, testIdentifier.getUniqueId(), testExecutionResult.getThrowable().isPresent() 073 ? testExecutionResult.getThrowable().get() 074 :null); 075 } 076 ModuleTestExecutor.printTestFinished(out, 077 testIdentifier.getDisplayName(), 078 testExecutionResult.getStatus().name() 079 ); 080 super.executionFinished(testIdentifier, testExecutionResult); 081 } 082 } 083 084 /** 085 * Execute all tests defined in this module and return a TestExecutionSummary. 086 * 087 * Any stack traces required to diagnose test failures will be output at this 088 * stage, along with a unique test ID. 089 * 090 * @param out the PrintStream to log execution or test failure. 091 * @param classNameFilter a Regex to use to select tests. If null, this will 092 * default to the JUnit console filter. 093 * @return The test execution summary. 094 * 095 * @see org.junit.platform.engine.discovery.ClassNameFilter 096 */ 097 public CliTestSummary executeTests(PrintStream out, String classNameFilter) { 098 LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 099 .selectors( 100 getPackageSelectors() 101 ) 102 .filters( 103 includeClassNamePatterns(classNameFilter == null ? DEFAULT_CLASSNAME_FILTER : classNameFilter) 104 ) 105 .build(); 106 107 ModuleTestExecutionListener moduleTestExecutionlistener = new ModuleTestExecutionListener(); 108 moduleTestExecutionlistener.setOut(out); 109 110 try (LauncherSession session = LauncherFactory.openSession()) { 111 Launcher launcher = session.getLauncher(); 112 113 launcher.registerTestExecutionListeners(moduleTestExecutionlistener); 114 115 TestPlan testPlan = launcher.discover(request); 116 // Execute test plan 117 launcher.execute(testPlan); 118 // Alternatively, execute the request directly 119 // launcher.execute(request); 120 } 121 122 TestExecutionSummary summary = moduleTestExecutionlistener.getSummary(); 123 124 out.println("\n" + String.format(MODULE_FINISHED_TEMPLATE, getModuleName())); 125 out.println(String.format(SUMMARY_TEMPLATE, summary.getTestsFoundCount(), summary.getTestsFailedCount(), summary.getTestsAbortedCount(), summary.getTestsSkippedCount())); 126 127 return new JUnit5TestSummaryAdapter(summary); 128 } 129 130 131 132 /** 133 * Create a ModuleTestExecutor for the standard case where the module name 134 * maps to the package name of the root package containing tests. 135 * 136 * For example the module "org.hl7.fhir.r5" will be assumed to have all its 137 * tests in the "org.hl7.fhir.r5" package or its child packages. 138 * 139 * @param moduleName The name of the module and root package containing tests. 140 * @return the ModuleTestExecutor for the module 141 */ 142 public static JUnit5ModuleTestExecutor getStandardModuleTestExecutor(String moduleName) { 143 return new JUnit5ModuleTestExecutor(moduleName, Arrays.asList(moduleName)); 144 } 145}