Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
397 changes: 255 additions & 142 deletions README.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion java-reporter-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.13.1</version>
<version>0.14.0</version>
<packaging>jar</packaging>

<name>Testomat.io Reporter Core</name>
Expand Down Expand Up @@ -104,6 +104,12 @@
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.22.0</version>
<scope>compile</scope>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.testomat.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Artifact {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.testomat.core.constants;

public class CommonConstants {
public static final String REPORTER_VERSION = "0.13.1";
public static final String REPORTER_VERSION = "0.14.0";

public static final String TESTS_STRING = "tests";
public static final String API_KEY_STRING = "api_key";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Main public API facade for Testomat.io integration.
Expand Down Expand Up @@ -45,10 +44,6 @@ public static void stepArtifact(String... directories) {
}
TestStep testStep = StepLifecycle.current();

if (directories == null || directories.length == 0){
return;
}

if (testStep == null) {
testStep = StepLifecycle.lastFinished();
}
Expand Down Expand Up @@ -81,8 +76,9 @@ public static void step(String stepName, Runnable action) {
step.setStatus(StepStatus.failed);
step.setLog(getStackTrace(t));
step.setError(
Optional.ofNullable(t.getMessage())
.orElse(t.getClass().getSimpleName())
t.getMessage() == null || t.getMessage().isBlank()
? t.getClass().getSimpleName()
: t.getClass().getSimpleName() + ": " + t.getMessage()
);
throwUnchecked(t);
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.testomat.core.facade.methods.artifact;

import io.testomat.core.facade.Testomatio;
import io.testomat.core.step.StepLifecycle;
import io.testomat.core.step.TestStep;
import java.io.File;
import java.nio.file.Path;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class ArtifactAspect {
private static final Logger log = LoggerFactory.getLogger(ArtifactAspect.class);

@AfterReturning(
pointcut = "@annotation(io.testomat.core.annotation.Artifact)",
returning = "result"
)
public void afterArtifact(Object result) {
String fileName = resolveFileName(result);
if (fileName == null) {
return;
}

TestStep testStep = StepLifecycle.current();
if (testStep == null) {
testStep = StepLifecycle.lastFinished();
}
if (testStep == null || testStep.getId() == null) {
Testomatio.artifact(fileName);
} else {
Testomatio.stepArtifact(fileName);
}
}

private String resolveFileName(Object result) {
if (result instanceof String) {
return (String) result;
}
if (result instanceof Path) {
return ((Path) result).toString();
}
if (result instanceof File) {
return ((File) result).getAbsolutePath();
}

log.warn(
"@Artifact ignored: method returned unsupported type '{}'. "
+ "Supported types are String, Path and File.",
result == null ? "null" : result.getClass().getName()
);

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public void storeStepDirectories(UUID stepId, String... directories) {
}

private void store(String[] directories, Consumer<String> storage, String logMessage) {
if (directories == null) {
return;
}

for (String dir : directories) {
if (!isValidFilePath(dir)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import static io.testomat.core.facade.Testomatio.stepArtifact;

import io.testomat.core.annotation.Step;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Aspect;
Expand Down Expand Up @@ -95,10 +96,10 @@ public void afterSuccess(JoinPoint joinPoint) {
* and completes the step lifecycle.
*
* @param joinPoint intercepted method invocation
* @param e thrown exception
* @param t thrown exception
*/
@AfterThrowing(pointcut = "stepAnnotation()", throwing = "e")
public void afterFailure(JoinPoint joinPoint, Throwable e) {
@AfterThrowing(pointcut = "stepAnnotation()", throwing = "t")
public void afterFailure(JoinPoint joinPoint, Throwable t) {
Step step = resolveStepAnnotation(joinPoint);
TestStep testStep = StepLifecycle.current();

Expand All @@ -113,20 +114,24 @@ public void afterFailure(JoinPoint joinPoint, Throwable e) {

testStep.setStatus(StepStatus.failed);
testStep.setDuration(duration);
testStep.setError(e.getMessage());
testStep.setLog(Arrays.toString(e.getStackTrace()));
testStep.setError(
t.getMessage() == null || t.getMessage().isBlank()
? t.getClass().getSimpleName()
: t.getClass().getSimpleName() + ": " + t.getMessage()
);
testStep.setLog(getStackTrace(t));

if (artifacts != null) {
stepArtifact(artifacts);
}

log.debug("Step '{}' failed in {} ms", stepName, duration, e);
log.debug("Step '{}' failed in {} ms", stepName, duration, t);

StepLifecycle.finish();
}

private long calculateDuration(String stepId) {
return System.currentTimeMillis() - StepTimer.stop(stepId);
return StepTimer.stop(stepId);
}

private Step resolveStepAnnotation(JoinPoint joinPoint) {
Expand Down Expand Up @@ -215,4 +220,10 @@ private String substituteParameters(String stepName, JoinPoint joinPoint) {
private String format(Object value) {
return value == null ? "null" : value.toString();
}

private static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
1 change: 1 addition & 0 deletions java-reporter-core/src/main/resources/META-INF/aop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<aspectj>
<aspects>
<aspect name="io.testomat.core.step.StepAspect"/>
<aspect name="io.testomat.core.facade.methods.artifact.ArtifactAspect"/>
</aspects>

<weaver>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package io.testomat.core.artifact;

import io.testomat.core.facade.Testomatio;
import io.testomat.core.facade.methods.artifact.ArtifactAspect;
import io.testomat.core.step.StepLifecycle;
import io.testomat.core.step.TestStep;
import java.io.File;
import java.nio.file.Path;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import static org.mockito.Mockito.*;

class ArtifactAspectTest {

private final ArtifactAspect aspect = new ArtifactAspect();

@Test
void shouldSendStringArtifact() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

lifecycle.when(StepLifecycle::current).thenReturn(null);
lifecycle.when(StepLifecycle::lastFinished).thenReturn(null);

aspect.afterArtifact("file.txt");

testomatio.verify(() -> Testomatio.artifact("file.txt"));
}
}

@Test
void shouldSendPathArtifact() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

lifecycle.when(StepLifecycle::current).thenReturn(null);
lifecycle.when(StepLifecycle::lastFinished).thenReturn(null);

Path path = Path.of("file.txt");

aspect.afterArtifact(path);

testomatio.verify(() -> Testomatio.artifact(path.toString()));
}
}

@Test
void shouldSendFileArtifact() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

lifecycle.when(StepLifecycle::current).thenReturn(null);
lifecycle.when(StepLifecycle::lastFinished).thenReturn(null);

File file = new File("file.txt");

aspect.afterArtifact(file);

testomatio.verify(() -> Testomatio.artifact(file.getAbsolutePath()));
}
}

@Test
void shouldSendStepArtifactWhenCurrentStepExists() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

TestStep step = mock(TestStep.class);

when(step.getId()).thenReturn(UUID.randomUUID());

lifecycle.when(StepLifecycle::current).thenReturn(step);

aspect.afterArtifact("file.txt");

testomatio.verify(() -> Testomatio.stepArtifact("file.txt"));
}
}

@Test
void shouldSendStepArtifactWhenLastFinishedStepExists() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

TestStep step = mock(TestStep.class);

when(step.getId()).thenReturn(UUID.randomUUID());

lifecycle.when(StepLifecycle::current).thenReturn(null);
lifecycle.when(StepLifecycle::lastFinished).thenReturn(step);

aspect.afterArtifact("file.txt");

testomatio.verify(() -> Testomatio.stepArtifact("file.txt"));
}
}

@Test
void shouldSendArtifactWhenStepHasNullId() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

TestStep step = mock(TestStep.class);

when(step.getId()).thenReturn(null);

lifecycle.when(StepLifecycle::current).thenReturn(step);

aspect.afterArtifact("file.txt");

testomatio.verify(() -> Testomatio.artifact("file.txt"));
}
}

@Test
void shouldIgnoreUnsupportedType() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

aspect.afterArtifact(new Object());

testomatio.verifyNoInteractions();
}
}

@Test
void shouldIgnoreNull() {
try (MockedStatic<StepLifecycle> lifecycle = mockStatic(StepLifecycle.class);
MockedStatic<Testomatio> testomatio = mockStatic(Testomatio.class)) {

aspect.afterArtifact(null);

testomatio.verifyNoInteractions();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ private void stepThatThrows() {
throw new RuntimeException("Test exception");
}

@Step("Failing step")
void stepThatThrowsWithoutMessage() {
throw new RuntimeException();
}

@Step("Process user: {0}")
private void stepWithComplexObject(User user) {
log.info("Processing user: {}", user);
Expand Down Expand Up @@ -244,10 +249,17 @@ void testFailureStatus(){
}

@Test
void testErrorMessageStored(){
void testErrorMessageStored() {
assertThrows(RuntimeException.class, this::stepThatThrows);
TestStep step= StepStorage.getSteps().get(0);
assertEquals("Test exception", step.getError());
TestStep step = StepStorage.getSteps().get(0);
assertTrue(step.getError().contains("Test exception"));
}

@Test
void shouldStoreExceptionClassWhenMessageIsNull() {
assertThrows(RuntimeException.class, this::stepThatThrowsWithoutMessage);
TestStep step = StepStorage.getSteps().get(0);
assertEquals("RuntimeException", step.getError());
}

@Test
Expand Down
Loading
Loading