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
32 changes: 32 additions & 0 deletions allure-hamcrest/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
description = "Allure Hamcrest Assertions Integration"

val agent: Configuration by configurations.creating

dependencies {
agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
compileOnly("org.aspectj:aspectjrt")
implementation("org.hamcrest:hamcrest")
testAnnotationProcessor(project(":allure-descriptions-javadoc"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.assertj:assertj-core")
testImplementation("org.slf4j:slf4j-simple")
testImplementation(project(":allure-java-commons-test"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks.jar {
manifest {
attributes(mapOf(
"Automatic-Module-Name" to "io.qameta.allure.hamcrest"
))
}
}

tasks.test {
useJUnitPlatform()
doFirst {
jvmArgs("-javaagent:${agent.singleFile}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2021 Qameta Software OÜ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.qameta.allure.hamcrest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.AfterReturning;
import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.StepResult;
import io.qameta.allure.util.ObjectUtils;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;

import java.util.UUID;

import static io.qameta.allure.util.ResultsUtils.getStatus;

/**
* <p>
* Aspect "interceptor" for automatic logging to the Allure report.
* </p>
* <p>
* This aspect should work for all asserts that are in the Hamcrest library, since they all go through a single method
* to start the comparison.
* </p>
* <p>
* In addition to the standard comparisons that are already in the Hamcrest library, this aspect should work correctly
* with custom matchers if developers correctly implemented the describeMismatch and / or describeMismatchSafely
* methods in the TypeSafeMatcher class.
* </p>
*
* @author a-simeshin (Simeshin Artem)
* @see org.hamcrest.TypeSafeMatcher
*/
@Aspect
@SuppressWarnings("all")
public class AllureHamcrestAssert {

private static InheritableThreadLocal<AllureLifecycle> lifecycle = new InheritableThreadLocal<AllureLifecycle>() {
@Override
protected AllureLifecycle initialValue() {
return Allure.getLifecycle();
}
};

public static AllureLifecycle getLifecycle() {
return lifecycle.get();
}

@Pointcut("execution(void org.hamcrest.MatcherAssert.**(..))")
public void initAssertThat() {
}

/**
* <p>
* assertThat(String comment, Object actual, Matcher expected) - one and only one central entry point for all
* asserts. Based on this rule, you only need to log a method with three arguments.
* </p>
* <p>
* Even if there is no comment as the first argument, an empty string will be passed as the first argument.
* </p>
* <p>
* For example assertThat(value123.get(), is(equalTo("value123"))) will be proxied to the metod
* assertThat("", value123.get(), is(equalTo("value123")))
* </p>
*
* @param joinPoint - entry point with args and method name
*/
@Before("initAssertThat()")
public void catchAndStartStep(final JoinPoint joinPoint) {
if (joinPoint.getArgs().length == 3) {
final String reason = (String) joinPoint.getArgs()[0];
final String actual = ObjectUtils.toString(joinPoint.getArgs()[1]);
final StringDescription description = new StringDescription();
final String expecting = description.appendText("assert \"")
.appendText(actual)
.appendText("\" ")
.appendDescriptionOf((Matcher) joinPoint.getArgs()[2])
.toString();

getLifecycle().startStep(
UUID.randomUUID().toString(),
new StepResult()
.setName(reason.isEmpty() ? expecting : expecting + " | " + reason)
.setDescription("Hamcrest assert")
.setStatus(Status.PASSED)
);
}
}

@AfterThrowing(pointcut = "initAssertThat()", throwing = "e")
public void stepFailed(final Throwable e) {
getLifecycle().updateStep(s -> s.setStatus(getStatus(e).orElse(Status.BROKEN)));
getLifecycle().stopStep();
}

@AfterReturning(pointcut = "initAssertThat()")
public void stepStop() {
getLifecycle().updateStep(s -> s.setStatus(Status.PASSED));
getLifecycle().stopStep();
}

/**
* For tests only.
*
* @param allure allure lifecycle to set
*/
public static void setLifecycle(final AllureLifecycle allure) {
lifecycle.set(allure);
}
}
6 changes: 6 additions & 0 deletions allure-hamcrest/src/main/resources/META-INF/aop-ajc.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<aspectj>
<weaver options="-warn:none -Xlint:ignore"/>
<aspects>
<aspect name="io.qameta.allure.hamcrest.AllureHamcrestAssert"/>
</aspects>
</aspectj>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2021 Qameta Software OÜ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.qameta.allure.hamcrest;

import io.qameta.allure.model.StepResult;
import io.qameta.allure.model.TestResult;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import static io.qameta.allure.test.RunUtils.runWithinTestContext;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;

/**
* This tests should cover cases when reason string exists in assertion.
*/
@SuppressWarnings("all")
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
public class AllureHamcrestAssertionNameContainsReasonTest {

@Test
void hamcrestAssertNameWithComment() {
final TestResult testResult = runWithinTestContext(
() -> assertThat(
"Business always likes something weird",
"TheBiscuit",
equalToIgnoringCase("thebiscuit")
),
AllureHamcrestAssert::setLifecycle
).getTestResults().get(0);

Assertions.assertThat(testResult.getSteps())
.flatExtracting(StepResult::getName)
.containsExactly("assert \"TheBiscuit\" a string equal to \"thebiscuit\" ignoring case | Business always likes something weird");
}

@Test
void hamcrestAssertNameWoComment() {
final TestResult testResult = runWithinTestContext(
() -> assertThat(
"TheBiscuit",
equalToIgnoringCase("thebiscuit")
),
AllureHamcrestAssert::setLifecycle
).getTestResults().get(0);

Assertions.assertThat(testResult.getSteps())
.flatExtracting(StepResult::getName)
.containsExactly("assert \"TheBiscuit\" a string equal to \"thebiscuit\" ignoring case");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2021 Qameta Software OÜ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.qameta.allure.hamcrest;

import io.qameta.allure.model.StepResult;
import io.qameta.allure.model.TestResult;
import org.assertj.core.api.Assertions;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static io.qameta.allure.test.RunUtils.runWithinTestContext;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

/**
* All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Collections" section
* <ui>
* <li>array</li>
* <li>hasEntry</li>
* <li>hasKey</li>
* <li>hasValue</li>
* <li>hasItem</li>
* <li>hasItems</li>
* <li>hasItemInArray</li>
* </ui>
*/
@SuppressWarnings("all")
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
public class AllureHamcrestCollectionsMatchersTest {

@Test
void hamcrestAssertNameForArrayMatchers() {
final TestResult testResult = runWithinTestContext(
() -> assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3)))),
AllureHamcrestAssert::setLifecycle
).getTestResults().get(0);

Assertions.assertThat(testResult.getSteps())
.flatExtracting(StepResult::getName)
.containsExactly("assert \"[1, 2, 3]\" is [<1>, <2>, <3>]");
}

private static Stream<Arguments> mapTestCases() {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);

return Stream.of(
Arguments.of(
map, hasEntry(equalTo("key1"), equalTo(1)),
"assert \"{key1=1, key2=2, key3=3}\" map containing [\"key1\"-><1>]"
),
Arguments.of(
map, hasKey(equalTo("key2")),
"assert \"{key1=1, key2=2, key3=3}\" map containing [\"key2\"->ANYTHING]"
),
Arguments.of(
map, hasValue(equalTo(3)),
"assert \"{key1=1, key2=2, key3=3}\" map containing [ANYTHING-><3>]"
)
);
}

@ParameterizedTest
@MethodSource("mapTestCases")
void hamcrestAssertNameForMapMatchers(Map actual, Matcher matcher, String expectedName) {
final TestResult testResult = runWithinTestContext(
() -> assertThat(actual, matcher),
AllureHamcrestAssert::setLifecycle
).getTestResults().get(0);

Assertions.assertThat(testResult.getSteps())
.flatExtracting(StepResult::getName)
.containsExactly(expectedName);
}

private static Stream<Arguments> iterableTestCases() {
List<String> list = Arrays.asList("val1", "val2", "val3");

return Stream.of(
Arguments.of(
list, hasItem(equalTo("key1")),
"assert \"[val1, val2, val3]\" a collection containing \"key1\""
),
Arguments.of(
list, hasItems(startsWith("v"), endsWith("l2")),
"assert \"[val1, val2, val3]\" (a collection containing a string starting with \"v\" and a collection containing a string ending with \"l2\")"
),
Arguments.of(
list, hasItemInArray(Arrays.asList("val1", "val2")),
"assert \"[val1, val2, val3]\" an array containing <[val1, val2]>"
)
);
}

@ParameterizedTest
@MethodSource("iterableTestCases")
void hamcrestAssertNameForIterableMatchers(Iterable actual, Matcher matcher, String expectedName) {
final TestResult testResult = runWithinTestContext(
() -> assertThat(actual, matcher),
AllureHamcrestAssert::setLifecycle
).getTestResults().get(0);

Assertions.assertThat(testResult.getSteps())
.flatExtracting(StepResult::getName)
.containsExactly(expectedName);
}
}
Loading