diff --git a/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/annotation/Seed.java b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/annotation/Seed.java new file mode 100644 index 000000000..4b44a7972 --- /dev/null +++ b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/annotation/Seed.java @@ -0,0 +1,32 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * 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 com.navercorp.fixturemonkey.junit.jupiter.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 Seed { + /** + * This value is used to set the seed for generating random numbers. + */ + long value() default 0L; +} diff --git a/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java new file mode 100644 index 000000000..f59ca4348 --- /dev/null +++ b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java @@ -0,0 +1,71 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * 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 com.navercorp.fixturemonkey.junit.jupiter.extension; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.navercorp.fixturemonkey.api.random.Randoms; +import com.navercorp.fixturemonkey.junit.jupiter.annotation.Seed; + +public final class FixtureMonkeySeedExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + private static final ThreadLocal SEED_HOLDER = new ThreadLocal<>(); + + private static final Logger LOGGER = LoggerFactory.getLogger(FixtureMonkeySeedExtension.class); + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + Seed seed = context.getRequiredTestMethod().getAnnotation(Seed.class); + if (seed != null) { + setSeed(seed.value()); + } + } + + /** + * Logs the seed used for the test if the test fails. + * This method is called after a test method has executed. + * If the test failed, it logs the seed used for the test. + **/ + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + if (context.getExecutionException().isPresent()) { + logSeedIfTestFailed(context); + } + } + + /** + * Sets the seed for generating random numbers. + **/ + private void setSeed(long seed) { + SEED_HOLDER.set(Randoms.create(String.valueOf(seed)).nextLong()); + } + + /** + * Logs the seed if the test failed. + * This method logs the seed value when a test method execution fails. + **/ + private void logSeedIfTestFailed(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + LOGGER.error(String.format("Test Method [%s] failed with seed: %d", testMethod.getName(), SEED_HOLDER.get())); + } +} diff --git a/fixture-monkey-junit-jupiter/src/test/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtensionTest.java b/fixture-monkey-junit-jupiter/src/test/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtensionTest.java new file mode 100644 index 000000000..6da4e75e9 --- /dev/null +++ b/fixture-monkey-junit-jupiter/src/test/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtensionTest.java @@ -0,0 +1,161 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * 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 com.navercorp.fixturemonkey.junit.jupiter.extension; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import lombok.Data; + +import com.navercorp.fixturemonkey.FixtureMonkey; +import com.navercorp.fixturemonkey.api.type.TypeReference; +import com.navercorp.fixturemonkey.junit.jupiter.annotation.Seed; + +@ExtendWith(FixtureMonkeySeedExtension.class) +class FixtureMonkeySeedExtensionTest { + private static final FixtureMonkey SUT = FixtureMonkey.create(); + private static ListAppender EVENT_APPENDER; + + @BeforeAll + static void setup() { + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + + EVENT_APPENDER = new ListAppender<>(); + EVENT_APPENDER.setContext(loggerContext); + EVENT_APPENDER.start(); + + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(EVENT_APPENDER); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.ERROR); + + } + + @AfterEach + void tearDown() { + EVENT_APPENDER.list.clear(); + } + + @Seed(1234L) + @Test + void testWithSeed() { + boolean logFound = false; + try { + Product product = SUT.giveMeBuilder(Product.class) + .set("id", 1000L) + .set("productName", "Book") + .sample(); + + assertAll( + () -> assertNotNull(product), + () -> assertEquals(2000L, (long)product.getId()), + () -> assertEquals("Computer", product.getProductName()) + ); + } catch (AssertionError e) { + List logs = EVENT_APPENDER.list; + logFound = logs.stream() + .anyMatch(event -> event.getFormattedMessage() + .contains("Test Method [testWithSeed] failed with seed: ")); + } + assertTrue(logFound, "Expected log message found."); + } + + @Seed(1) + @RepeatedTest(100) + void seedReturnsSame() { + String expected = "섨ꝓ仛禦催ᘓ蓊類౺阹瞻塢飖獾ࠒ⒐፨婵얎⽒竻·俌欕悳잸횑ٻ킐結"; + + String actual = SUT.giveMeOne(String.class); + + then(actual).isEqualTo(expected); + } + + @Seed(1) + @RepeatedTest(100) + void latterValue() { + String expected = "聩ዡ㘇뵥刲禮ᣮ鎊熇捺셾壍Ꜻꌩ垅凗❉償粐믩࠱哠횛"; + SUT.giveMeOne(String.class); + + String actual = SUT.giveMeOne(String.class); + + then(actual).isEqualTo(expected); + } + + @Seed(1) + @RepeatedTest(100) + void containerReturnsSame() { + List expected = Collections.singletonList("仛禦催ᘓ蓊類౺阹瞻塢飖獾ࠒ⒐፨婵얎⽒竻·俌欕悳잸횑ٻ킐結"); + + List actual = SUT.giveMeOne(new TypeReference>() { + }); + + then(actual).isEqualTo(expected); + } + + @Seed(1) + @RepeatedTest(100) + void containerMattersOrder() { + Set expected = new HashSet<>(Collections.singletonList("仛禦催ᘓ蓊類౺阹瞻塢飖獾ࠒ⒐፨婵얎⽒竻·俌欕悳잸횑ٻ킐結")); + + Set actual = SUT.giveMeOne(new TypeReference>() { + }); + + then(actual).isEqualTo(expected); + } + + @Seed(1) + @RepeatedTest(100) + void multipleContainerReturnsDiff() { + Set firstSet = SUT.giveMeOne(new TypeReference>() { + }); + + List secondList = SUT.giveMeOne(new TypeReference>() { + }); + + then(firstSet).isNotEqualTo(secondList); + } + + @Data + private static class Product { + @NotNull + private Long id; + @NotBlank + private String productName; + } +} diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java index 17da398f4..8c80558a4 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java @@ -510,6 +510,10 @@ public FixtureMonkeyBuilder pushJavaConstraintGeneratorCustomizer( return this; } + /** + * It is deprecated. Please use {@code @Seed} in fixture-monkey-junit-jupiter module. + */ + @Deprecated public FixtureMonkeyBuilder seed(long seed) { this.seed = seed; return this;