This repository has been archived by the owner on Feb 25, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…st-listener to master * commit '4ba25c979e94504648fe3b460e548f091dc92743': #11 renamed module and package to maven-utils and maven.utils to make it more clear, what it is #11 junit is a compile dependency of course #11 added test scope to dependency #11 - added test listener, which generates a suite class into the target folder with exactly the same test class order as it was during the build
- Loading branch information
Showing
10 changed files
with
370 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>parent</artifactId> | ||
<groupId>at.willhaben.willtest</groupId> | ||
<version>1.1.2</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
<artifactId>maven-utils</artifactId> | ||
<name>${project.groupId}:${project.artifactId}</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.sun</groupId> | ||
<artifactId>tools</artifactId> | ||
<scope>system</scope> | ||
<version>${java.version}</version> | ||
<systemPath>${toolsjar}</systemPath> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.github.stefanbirkner</groupId> | ||
<artifactId>system-rules</artifactId> | ||
<version>1.16.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>20.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.jcabi</groupId> | ||
<artifactId>jcabi-matchers</artifactId> | ||
<version>1.3</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<profiles> | ||
<profile> | ||
<id>default-profile</id> | ||
<activation> | ||
<file> | ||
<exists>${java.home}/../lib/tools.jar</exists> | ||
</file> | ||
</activation> | ||
<properties> | ||
<toolsjar>${java.home}/../lib/tools.jar</toolsjar> | ||
</properties> | ||
</profile> | ||
<profile> | ||
<id>mac-profile</id> | ||
<activation> | ||
<file> | ||
<exists>${java.home}/../Classes/classes.jar</exists> | ||
</file> | ||
</activation> | ||
<properties> | ||
<toolsjar>${java.home}/../Classes/classes.jar</toolsjar> | ||
</properties> | ||
</profile> | ||
</profiles> | ||
</project> |
131 changes: 131 additions & 0 deletions
131
maven-utils/src/main/java/at/willhaben/willtest/maven/utils/SuiteGeneratingRunListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package at.willhaben.willtest.maven.utils; | ||
|
||
import com.sun.codemodel.internal.*; | ||
import org.junit.runner.Description; | ||
import org.junit.runner.Result; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runner.notification.RunListener; | ||
import org.junit.runners.Suite; | ||
|
||
import java.io.File; | ||
import java.text.SimpleDateFormat; | ||
import java.util.*; | ||
|
||
/** | ||
* Generates a test suite class into maven target folder, which will contain the test classes in the execution order.<br/> | ||
* The class will have the FQN {@value SUITE_TARGET_CLASS_NAME_PREFIX}{@value DATE_FORMAT}.<br/> | ||
* The javadoc of the generated class will contain a table where the method order can be also seen.<br/> | ||
* How to use it? See at the <a href="http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html#Using_Custom_Listeners_and_Reporters">official surefire documentation site.</a> | ||
*/ | ||
public class SuiteGeneratingRunListener extends RunListener { | ||
static final String SUITE_TARGET_CLASS_NAME_PREFIX = "at.willhaben.willtest.generated.Suite"; | ||
private static final String MAVEN_MODULE_DIRECTORY_SYSTEM_PROPERTY = "basedir"; | ||
private static final String TARGET = "target"; | ||
private static final String VALUE_PARAM = "value"; | ||
public static final String DATE_FORMAT = "_yyyyMMdd_HHmmss"; | ||
private Map<String,String> importedTestClasses = new HashMap<>(); | ||
|
||
/** | ||
* {@link LinkedHashMap} to preserve insertion order | ||
*/ | ||
private LinkedHashMap<String,List<String>> testClassesAndTheirTests = new LinkedHashMap<>(); | ||
|
||
@Override | ||
public synchronized void testStarted(Description description) throws Exception { | ||
super.testStarted(description); | ||
String className = description.getClassName(); | ||
List<String> testDescriptions = testClassesAndTheirTests.computeIfAbsent(className, key -> new ArrayList<>()); | ||
testDescriptions.add("Method '" + description.getMethodName() + "', DisplayName: '" + description.getDisplayName() + "'" ); | ||
} | ||
|
||
@Override | ||
public void testRunFinished(Result result) throws Exception { | ||
super.testRunFinished(result); | ||
JCodeModel codeModel = new JCodeModel(); | ||
JDefinedClass resultClass = codeModel._class(getSuiteClassName()); | ||
JClass suiteClazz = codeModel.ref(Suite.class); | ||
resultClass.annotate(RunWith.class).param(VALUE_PARAM, suiteClazz); | ||
|
||
JClass suiteClasses = codeModel.ref(Suite.SuiteClasses.class); | ||
|
||
JAnnotationArrayMember testClassArray = resultClass.annotate(suiteClasses).paramArray(VALUE_PARAM); | ||
|
||
testClassesAndTheirTests.keySet().forEach(className -> addClassToSuite(codeModel, testClassArray, className)); | ||
|
||
resultClass.javadoc().add(getJavaDocComment()); | ||
|
||
File file = new File(getTargetDirectory()); | ||
if ( !file.exists() && !file.mkdirs() ) { | ||
throw new RuntimeException("Cannot create folder " + file.getAbsolutePath()); | ||
} | ||
codeModel.build(file); | ||
} | ||
|
||
private void addClassToSuite(JCodeModel codeModel, JAnnotationArrayMember testClassArray, String className) { | ||
try { | ||
Class testClass = Class.forName(className); | ||
String simpleName = testClass.getSimpleName(); | ||
if ( !importedTestClasses.containsKey(simpleName) ) { | ||
JClass importedClass = codeModel.ref(testClass); | ||
importedTestClasses.put(simpleName,className); | ||
testClassArray.param(importedClass); | ||
} else { | ||
testClassArray.param(testClass); | ||
} | ||
} catch (ClassNotFoundException e) { | ||
throw new IllegalArgumentException("Cannot add class with name " + className + "!", e); | ||
} | ||
} | ||
|
||
private String getJavaDocComment( ) { | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append("Test suite generated by ") | ||
.append(SuiteGeneratingRunListener.class.getName()) | ||
.append(" based on execution order in surefire/failsafe maven plugin. " + | ||
"Below you see the classes in order of execution and in the second column " + | ||
"the test method order inside the class file.<br/>\n") | ||
.append("<table>\n") | ||
.append("<tr>\n\t<th>Test class</th><th>Test method and display name</th>\n</tr>\n"); | ||
testClassesAndTheirTests.forEach( (clazz, methods) -> sb.append(testClassToHTML(clazz, methods))); | ||
sb.append("</table>"); | ||
return sb.toString(); | ||
} | ||
|
||
private String testClassToHTML(String key, List<String> value) { | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append("<tr>\n"); | ||
if ( value.size() > 1) { | ||
sb.append("\t<td rowspan=\"").append(value.size()).append("\">"); | ||
} | ||
else { | ||
sb.append("\t<td>"); | ||
} | ||
sb.append(key).append("</td>"); | ||
for( int i = 0; i < value.size(); i++ ) { | ||
if ( i == 0 ) { | ||
sb.append("<td>").append(value.get(i)).append("</td>\n</tr>\n"); | ||
} | ||
else { | ||
sb.append("<tr>\n\t<td>").append(value.get(i)).append("</td>\n</tr>\n"); | ||
} | ||
} | ||
return sb.toString(); | ||
} | ||
|
||
private String getTargetDirectory() { | ||
return Optional.ofNullable(System.getProperty(MAVEN_MODULE_DIRECTORY_SYSTEM_PROPERTY)).orElse(".") | ||
+ File.separator + TARGET; | ||
} | ||
|
||
private String getSuiteClassName() { | ||
return SUITE_TARGET_CLASS_NAME_PREFIX + getTimeStamp(); | ||
} | ||
|
||
private String getTimeStamp() { | ||
return new SimpleDateFormat(DATE_FORMAT).format(getCurrentDate()); | ||
} | ||
|
||
Date getCurrentDate() { | ||
return new Date(); | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
...utils/src/test/java/at/willhaben/willtest/maven/utils/SuiteGeneratingRunListenerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package at.willhaben.willtest.maven.utils; | ||
|
||
import com.google.common.io.ByteStreams; | ||
import com.google.common.io.Files; | ||
import org.junit.Before; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.contrib.java.lang.system.RestoreSystemProperties; | ||
import org.junit.rules.TemporaryFolder; | ||
import org.junit.runner.Description; | ||
import org.junit.runner.Result; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.InputStream; | ||
import java.nio.charset.StandardCharsets; | ||
import java.text.ParseException; | ||
import java.text.SimpleDateFormat; | ||
import java.util.Date; | ||
|
||
import static com.jcabi.matchers.RegexMatchers.containsPattern; | ||
import static org.hamcrest.CoreMatchers.*; | ||
import static org.junit.Assert.assertThat; | ||
import static org.mockito.Mockito.*; | ||
|
||
public class SuiteGeneratingRunListenerTest { | ||
private static String FIX_DATE = "20170211_142342"; | ||
@Rule | ||
public TemporaryFolder temporaryFolder = new TemporaryFolder(); | ||
|
||
@Rule | ||
public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); | ||
|
||
private SuiteGeneratingRunListener suiteGeneratingRunListener; | ||
|
||
@Before | ||
public void prepare() throws ParseException { | ||
ExposingSuiteGeneratingRunListener spy = spy(new ExposingSuiteGeneratingRunListener()); | ||
doReturn(new SimpleDateFormat("yyyyMMdd_HHmmss").parse(FIX_DATE)) | ||
.when(spy) | ||
.getCurrentDate(); | ||
suiteGeneratingRunListener = spy; | ||
System.setProperty("basedir", temporaryFolder.getRoot().getAbsolutePath()); | ||
} | ||
|
||
@Test | ||
public void test() throws Exception { | ||
//for the sake of testing I just use simple jdk classes | ||
suiteGeneratingRunListener.testStarted( | ||
descriptionStubOf(Long.class.getName(), "methodA", "methodA[alma]")); | ||
suiteGeneratingRunListener.testStarted( | ||
descriptionStubOf(Long.class.getName(), "methodC", "methodC[alma]")); | ||
//to test importing only one class in case of name conflict I use here 2 Date classes | ||
suiteGeneratingRunListener.testStarted( | ||
descriptionStubOf(java.util.Date.class.getName(), "methodB", "methodB[alma]")); | ||
suiteGeneratingRunListener.testStarted( | ||
descriptionStubOf(java.sql.Date.class.getName(), "methodBB", "methodBB[alma]")); | ||
suiteGeneratingRunListener.testStarted( | ||
descriptionStubOf(FileInputStream.class.getName(), "methodX", "methodX[alma]")); | ||
suiteGeneratingRunListener.testRunFinished(mock(Result.class)); | ||
String name = SuiteGeneratingRunListener.SUITE_TARGET_CLASS_NAME_PREFIX.replace('.', File.separatorChar) + "_" + FIX_DATE + ".java"; | ||
File expectedTargetFile = new File( | ||
temporaryFolder.getRoot().getAbsolutePath() + File.separator + | ||
"target" + File.separator + name); | ||
assertThat(expectedTargetFile.exists(),is(true)); | ||
String actualContent = convertWindowsNewLineToLinux(Files.toString(expectedTargetFile, StandardCharsets.UTF_8)); | ||
assertThat( | ||
"Util date should be imported, since the first class is imported even if simple name collision happens.", | ||
actualContent, | ||
containsString("import java.util.Date;")); | ||
assertThat( | ||
"Sql date should not be imported, since util date is already imported.", | ||
actualContent, | ||
not(containsString("import java.sql.Date;"))); | ||
assertThat( | ||
"Sql date should be in annotation with FQN present.", | ||
actualContent, | ||
containsPattern("\\sjava.sql.Date.class,")); | ||
assertThat( | ||
"Util date should be in annotation with simple name present.", | ||
actualContent, | ||
containsPattern("\\sDate.class,")); | ||
try ( InputStream expectedFileAsStream = this.getClass().getResourceAsStream("/ExpectedSuite.java") ) { | ||
String expectedFileContent = convertWindowsNewLineToLinux( | ||
new String( ByteStreams.toByteArray(expectedFileAsStream), StandardCharsets.UTF_8 )); | ||
assertThat(actualContent,is(expectedFileContent)); | ||
} | ||
} | ||
|
||
private String convertWindowsNewLineToLinux(String input) { | ||
return input.replaceAll("\r\n", "\n"); | ||
} | ||
|
||
private Description descriptionStubOf(String className, String methodName, String displayName) { | ||
Description description = mock(Description.class); | ||
when(description.getClassName()).thenReturn(className); | ||
when(description.getMethodName()).thenReturn(methodName); | ||
when(description.getDisplayName()).thenReturn(displayName); | ||
return description; | ||
} | ||
|
||
private static class ExposingSuiteGeneratingRunListener extends SuiteGeneratingRunListener { | ||
@Override | ||
public Date getCurrentDate() { | ||
return super.getCurrentDate(); | ||
} | ||
} | ||
} |
Oops, something went wrong.