-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andriy Tsarenko
committed
Apr 1, 2019
1 parent
d928493
commit 5b0fc6a
Showing
19 changed files
with
667 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns="http://maven.apache.org/POM/4.0.0" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>sprimber-parent</artifactId> | ||
<groupId>com.griddynamics.qa</groupId> | ||
<version>1.0.5-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>sprimber-reporter</artifactId> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.javassist</groupId> | ||
<artifactId>javassist</artifactId> | ||
<version>3.21.0-GA</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>26.0-jre</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-lang3</artifactId> | ||
<version>3.8.1</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter</artifactId> | ||
<version>2.0.2.RELEASE</version> | ||
<optional>true</optional> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<version>3.10.0</version> | ||
<optional>true</optional> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.projectlombok</groupId> | ||
<artifactId>lombok</artifactId> | ||
<version>1.18.2</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-javadoc-plugin</artifactId> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-source-plugin</artifactId> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
46 changes: 46 additions & 0 deletions
46
...main/java/com/griddynamics/qa/sprimber/reporter/TransformationClassLoaderInitializer.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,46 @@ | ||
package com.griddynamics.qa.sprimber.reporter; | ||
|
||
import com.griddynamics.qa.sprimber.reporter.classloader.TransformationClassLoader; | ||
import com.griddynamics.qa.sprimber.reporter.exception.TransformationClassLoaderInitializerException; | ||
import com.griddynamics.qa.sprimber.reporter.resolver.ReportByteCodeResolver; | ||
import lombok.extern.slf4j.Slf4j; | ||
import lombok.val; | ||
import org.springframework.context.ApplicationContextInitializer; | ||
import org.springframework.context.support.GenericApplicationContext; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
import static com.griddynamics.qa.sprimber.reporter.holder.ReportDescriptionServiceHolder.loadReportDescriptionServices; | ||
import static org.apache.commons.lang3.exception.ExceptionUtils.rethrow; | ||
import static org.springframework.core.io.support.SpringFactoriesLoader.loadFactories; | ||
|
||
@Slf4j | ||
public class TransformationClassLoaderInitializer implements ApplicationContextInitializer<GenericApplicationContext> { | ||
|
||
private static final AtomicBoolean INITIALIZE_TRANSFORMATION_CLASS_LOADER = new AtomicBoolean(true); | ||
|
||
@Override | ||
public void initialize(@Nonnull GenericApplicationContext context) { | ||
if (INITIALIZE_TRANSFORMATION_CLASS_LOADER.getAndSet(false)) { | ||
try { | ||
val originClassLoader = context.getClassLoader(); | ||
loadReportDescriptionServices(originClassLoader); | ||
val reportByteCodeResolvers = loadFactories(ReportByteCodeResolver.class, originClassLoader); | ||
val transformationClassLoader = new TransformationClassLoader(originClassLoader, reportByteCodeResolvers); | ||
context.setClassLoader(transformationClassLoader); | ||
for (ReportByteCodeResolver resolver : reportByteCodeResolvers) { | ||
for (String className : resolver.getSupportedClassNames()) { | ||
val transformedClass = transformationClassLoader.loadClass(className); | ||
log.debug("Class was transformed: " + transformedClass); | ||
} | ||
} | ||
} catch (Throwable e) { | ||
rethrow(new TransformationClassLoaderInitializerException( | ||
"Unable to initialize 'TransformationClassLoader', cause: " + e.getMessage(), e)); | ||
} | ||
} | ||
} | ||
|
||
} |
30 changes: 30 additions & 0 deletions
30
...ain/java/com/griddynamics/qa/sprimber/reporter/classloader/TransformationClassLoader.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,30 @@ | ||
package com.griddynamics.qa.sprimber.reporter.classloader; | ||
|
||
import com.griddynamics.qa.sprimber.reporter.resolver.ReportByteCodeResolver; | ||
|
||
import java.net.URL; | ||
import java.net.URLClassLoader; | ||
import java.util.List; | ||
|
||
public class TransformationClassLoader extends URLClassLoader { | ||
|
||
private final ClassLoader originClassLoader; | ||
private final List<ReportByteCodeResolver> reportByteCodeResolvers; | ||
|
||
public TransformationClassLoader(ClassLoader originClassLoader, | ||
List<ReportByteCodeResolver> reportByteCodeResolvers) { | ||
super(new URL[]{}, originClassLoader); | ||
this.originClassLoader = originClassLoader; | ||
this.reportByteCodeResolvers = reportByteCodeResolvers; | ||
} | ||
|
||
@Override | ||
public Class<?> loadClass(String name) throws ClassNotFoundException { | ||
return reportByteCodeResolvers.stream() | ||
.filter(resolver -> resolver.isSupportedClass(name)) | ||
.findFirst() | ||
.map(resolver -> resolver.insertReport(name, this, originClassLoader)) | ||
.orElse(super.loadClass(name)); | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
...java/com/griddynamics/qa/sprimber/reporter/exception/ReportByteCodeResolverException.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,9 @@ | ||
package com.griddynamics.qa.sprimber.reporter.exception; | ||
|
||
public class ReportByteCodeResolverException extends Throwable { | ||
|
||
public ReportByteCodeResolverException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
...ynamics/qa/sprimber/reporter/exception/TransformationClassLoaderInitializerException.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,9 @@ | ||
package com.griddynamics.qa.sprimber.reporter.exception; | ||
|
||
public class TransformationClassLoaderInitializerException extends Throwable { | ||
|
||
public TransformationClassLoaderInitializerException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
|
||
} |
34 changes: 34 additions & 0 deletions
34
...r/src/main/java/com/griddynamics/qa/sprimber/reporter/holder/ReportDescriptionHolder.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,34 @@ | ||
package com.griddynamics.qa.sprimber.reporter.holder; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import static com.google.common.collect.Lists.newArrayList; | ||
import static java.util.stream.Collectors.toList; | ||
|
||
@SuppressWarnings("unused") | ||
public class ReportDescriptionHolder { | ||
|
||
private static final Map<Class, List<String>> REPORTS_BY_SERVICE_CLASS = new ConcurrentHashMap<>(); | ||
|
||
public static void putReport(Class<?> serviceClass, String report) { | ||
REPORTS_BY_SERVICE_CLASS.computeIfPresent(serviceClass, (service, reports) -> { | ||
reports.add(report); | ||
return reports; | ||
}); | ||
REPORTS_BY_SERVICE_CLASS.putIfAbsent(serviceClass, newArrayList(report)); | ||
} | ||
|
||
public static Map<Class, List<String>> getReportsByServiceClass() { | ||
return REPORTS_BY_SERVICE_CLASS; | ||
} | ||
|
||
public static List<String> getReports() { | ||
return REPORTS_BY_SERVICE_CLASS.values().stream() | ||
.flatMap(Collection::stream) | ||
.collect(toList()); | ||
} | ||
|
||
} |
25 changes: 25 additions & 0 deletions
25
...ain/java/com/griddynamics/qa/sprimber/reporter/holder/ReportDescriptionServiceHolder.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,25 @@ | ||
package com.griddynamics.qa.sprimber.reporter.holder; | ||
|
||
import com.griddynamics.qa.sprimber.reporter.service.ReportDescriptionService; | ||
|
||
import java.util.List; | ||
|
||
import static java.util.stream.Collectors.toList; | ||
import static org.springframework.core.io.support.SpringFactoriesLoader.loadFactories; | ||
|
||
@SuppressWarnings("unused") | ||
public class ReportDescriptionServiceHolder { | ||
|
||
private static List<ReportDescriptionService> reportDescriptionServices; | ||
|
||
public static void loadReportDescriptionServices(ClassLoader originClassLoader) { | ||
reportDescriptionServices = loadFactories(ReportDescriptionService.class, originClassLoader); | ||
} | ||
|
||
public static List<ReportDescriptionService> findReportDescriptionServices(String className) { | ||
return reportDescriptionServices.stream() | ||
.filter(service -> service.getSupportedClassNames().contains(className)) | ||
.collect(toList()); | ||
} | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
sprimber-reporter/src/main/java/com/griddynamics/qa/sprimber/reporter/model/MethodInfo.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,15 @@ | ||
package com.griddynamics.qa.sprimber.reporter.model; | ||
|
||
import javassist.CtClass; | ||
import javassist.CtMethod; | ||
import lombok.Data; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Data | ||
@RequiredArgsConstructor | ||
public class MethodInfo { | ||
|
||
private final CtClass ctClass; | ||
private final CtMethod ctMethod; | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
...er-reporter/src/main/java/com/griddynamics/qa/sprimber/reporter/model/TestStatusType.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,8 @@ | ||
package com.griddynamics.qa.sprimber.reporter.model; | ||
|
||
public enum TestStatusType { | ||
|
||
SUCCESS, | ||
FAILED | ||
|
||
} |
129 changes: 129 additions & 0 deletions
129
...ain/java/com/griddynamics/qa/sprimber/reporter/resolver/CommonReportByteCodeResolver.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,129 @@ | ||
package com.griddynamics.qa.sprimber.reporter.resolver; | ||
|
||
import com.griddynamics.qa.sprimber.reporter.exception.ReportByteCodeResolverException; | ||
import com.griddynamics.qa.sprimber.reporter.model.MethodInfo; | ||
import com.griddynamics.qa.sprimber.reporter.model.TestStatusType; | ||
import javassist.CtClass; | ||
import javassist.CtMethod; | ||
import javassist.LoaderClassPath; | ||
import lombok.SneakyThrows; | ||
import lombok.val; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Stream; | ||
|
||
import static com.griddynamics.qa.sprimber.reporter.model.TestStatusType.FAILED; | ||
import static com.griddynamics.qa.sprimber.reporter.model.TestStatusType.SUCCESS; | ||
import static com.griddynamics.qa.sprimber.reporter.template.ReportMethodTemplates.PROCESS_REPORT_CODE_BLOCK_MASK; | ||
import static com.griddynamics.qa.sprimber.reporter.template.ReportMethodTemplates.ASSERTJ_SEND_REPORT_CODE_BLOCK_MASK; | ||
import static com.griddynamics.qa.sprimber.reporter.utils.ClassUtils.classToBytes; | ||
import static com.griddynamics.qa.sprimber.reporter.utils.ClassUtils.deFrostClass; | ||
import static java.lang.String.format; | ||
import static java.util.Arrays.stream; | ||
import static java.util.UUID.randomUUID; | ||
import static javassist.ClassPool.getDefault; | ||
import static org.apache.commons.lang3.StringUtils.EMPTY; | ||
import static org.apache.commons.lang3.exception.ExceptionUtils.rethrow; | ||
|
||
public abstract class CommonReportByteCodeResolver implements ReportByteCodeResolver { | ||
|
||
private static final Map<String, Class<?>> CACHED_TRANSFORMED_CLASSES = new ConcurrentHashMap<>(); | ||
|
||
private static final String TRANSFORMED_METHOD_NAME_PREFIX = "sr$"; | ||
private static final String METHOD_PARAM_PREFIX = "$"; | ||
private static final String METHDO_PARAM_DELIMITER = ","; | ||
private static final String METHOD_OPEN_BRACKET = "("; | ||
private static final String METHOD_CLOSED_BRACKET = ");"; | ||
|
||
private static final String WRAPPED_STRING_QUOTE = "\""; | ||
private static final String UUID_DELIMITER = "-"; | ||
|
||
protected Class<?> acceptMethodsTransformation(String className, | ||
ClassLoader transformationClassLoader, ClassLoader originClassLoader, | ||
Consumer<Stream<MethodInfo>> methodsInfoConsumer) { | ||
if (!CACHED_TRANSFORMED_CLASSES.containsKey(className)) { | ||
synchronized (CommonReportByteCodeResolver.class) { | ||
if (!CACHED_TRANSFORMED_CLASSES.containsKey(className)) { | ||
try { | ||
doAcceptMethodsTransformation(className, | ||
transformationClassLoader, originClassLoader, methodsInfoConsumer); | ||
} catch (Throwable e) { | ||
return rethrow(new ReportByteCodeResolverException( | ||
"Unable to transform methods, class: " + className, e)); | ||
} | ||
} | ||
} | ||
} | ||
return CACHED_TRANSFORMED_CLASSES.get(className); | ||
} | ||
|
||
protected void injectReportToMethod(MethodInfo methodInfo) { | ||
val assertionDescriptionParamIndex = findAssertionDescriptionParamIndex(methodInfo); | ||
if (assertionDescriptionParamIndex != -1) { | ||
setupReportProcessingToByteCode(methodInfo.getCtClass(), | ||
methodInfo.getCtMethod(), assertionDescriptionParamIndex); | ||
} | ||
} | ||
|
||
protected boolean isAlreadyTransformedMethod(CtMethod ctMethod) { | ||
return ctMethod.getName().startsWith(TRANSFORMED_METHOD_NAME_PREFIX); | ||
} | ||
|
||
protected abstract int findAssertionDescriptionParamIndex(MethodInfo methodInfo); | ||
|
||
@SneakyThrows | ||
private void doAcceptMethodsTransformation(String className, | ||
ClassLoader transformationClassLoader, ClassLoader originClassLoader, | ||
Consumer<Stream<MethodInfo>> methodsInfoConsumer) { | ||
val classPool = getDefault(); | ||
classPool.appendClassPath(new LoaderClassPath(transformationClassLoader)); | ||
val ctClass = classPool.makeClass(new ByteArrayInputStream(classToBytes(className, originClassLoader))); | ||
val methodsInfoStream = stream(ctClass.getMethods()) | ||
.map(ctMethod -> new MethodInfo(ctClass, ctMethod)); | ||
methodsInfoConsumer.accept(methodsInfoStream); | ||
CACHED_TRANSFORMED_CLASSES.put(className, ctClass.toClass()); | ||
} | ||
|
||
@SneakyThrows | ||
private void setupReportProcessingToByteCode(CtClass ctClass, CtMethod ctMethod, | ||
int assertionDescriptionParamIndex) { | ||
val transformedCtMethod = new CtMethod(ctMethod, ctClass, null); | ||
deFrostClass(ctClass); | ||
ctMethod.setName(generateMethodName()); | ||
val sendSuccessReportCodeBlock = buildSendReportCodeBlock(ctClass, assertionDescriptionParamIndex, SUCCESS); | ||
val sendFailedReportCodeBlock = buildSendReportCodeBlock(ctClass, assertionDescriptionParamIndex, FAILED); | ||
val transformedMethodCodeBlock = format(PROCESS_REPORT_CODE_BLOCK_MASK, | ||
buildInvokeOriginMethodCodeBlock(ctMethod), sendSuccessReportCodeBlock, sendFailedReportCodeBlock); | ||
transformedCtMethod.setBody(transformedMethodCodeBlock); | ||
ctClass.addMethod(transformedCtMethod); | ||
} | ||
|
||
private String generateMethodName() { | ||
return TRANSFORMED_METHOD_NAME_PREFIX + randomUUID().toString() | ||
.replaceAll(UUID_DELIMITER, EMPTY); | ||
} | ||
|
||
private String buildSendReportCodeBlock(CtClass ctClass, int assertionDescriptionParamIndex, | ||
TestStatusType testStatusType) { | ||
val status = WRAPPED_STRING_QUOTE + testStatusType + WRAPPED_STRING_QUOTE; | ||
val className = WRAPPED_STRING_QUOTE + ctClass.getName() + WRAPPED_STRING_QUOTE; | ||
return format(ASSERTJ_SEND_REPORT_CODE_BLOCK_MASK, className, | ||
assertionDescriptionParamIndex, assertionDescriptionParamIndex, status); | ||
} | ||
|
||
@SneakyThrows | ||
private String buildInvokeOriginMethodCodeBlock(CtMethod ctMethod) { | ||
val methodParametersCodeBlock = new StringBuilder(); | ||
for (int i = 0; i < ctMethod.getParameterTypes().length; i++) { | ||
methodParametersCodeBlock.append(METHOD_PARAM_PREFIX).append(i + 1); | ||
if ((i + 1) < ctMethod.getParameterTypes().length) { | ||
methodParametersCodeBlock.append(METHDO_PARAM_DELIMITER); | ||
} | ||
} | ||
return ctMethod.getName() + METHOD_OPEN_BRACKET + methodParametersCodeBlock.toString() + METHOD_CLOSED_BRACKET; | ||
} | ||
|
||
} |
13 changes: 13 additions & 0 deletions
13
.../src/main/java/com/griddynamics/qa/sprimber/reporter/resolver/ReportByteCodeResolver.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,13 @@ | ||
package com.griddynamics.qa.sprimber.reporter.resolver; | ||
|
||
import java.util.List; | ||
|
||
public interface ReportByteCodeResolver { | ||
|
||
List<String> getSupportedClassNames(); | ||
|
||
boolean isSupportedClass(String className); | ||
|
||
Class insertReport(String className, ClassLoader transformationClassLoader, ClassLoader originClassLoader); | ||
|
||
} |
Oops, something went wrong.