/*
 * Copyright 2015-2023 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package example;

// tag::user_guide[]

import static example.util.StringUtils.isPalindrome;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.junit.jupiter.api.Named.named;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import example.util.Calculator;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;

// end::user_guide[]
// @formatter:off
// tag::user_guide[]
class DynamicTestsDemo {

	private final Calculator calculator = new Calculator();

	// end::user_guide[]
	@Tag("exclude")
	// tag::user_guide[]
	// This will result in a JUnitException!
	@TestFactory
	List<String> dynamicTestsWithInvalidReturnType() {
		return Arrays.asList("Hello");
	}

	@TestFactory
	Collection<DynamicTest> dynamicTestsFromCollection() {
		return Arrays.asList(
			dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))),
			dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
		);
	}

	@TestFactory
	Iterable<DynamicTest> dynamicTestsFromIterable() {
		return Arrays.asList(
			dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))),
			dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
		);
	}

	@TestFactory
	Iterator<DynamicTest> dynamicTestsFromIterator() {
		return Arrays.asList(
			dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))),
			dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
		).iterator();
	}

	@TestFactory
	DynamicTest[] dynamicTestsFromArray() {
		return new DynamicTest[] {
			dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),
			dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
		};
	}

	@TestFactory
	Stream<DynamicTest> dynamicTestsFromStream() {
		return Stream.of("racecar", "radar", "mom", "dad")
			.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
	}

	@TestFactory
	Stream<DynamicTest> dynamicTestsFromIntStream() {
		// Generates tests for the first 10 even integers.
		return IntStream.iterate(0, n -> n + 2).limit(10)
			.mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
	}

	@TestFactory
	Stream<DynamicTest> generateRandomNumberOfTestsFromIterator() {

		// Generates random positive integers between 0 and 100 until
		// a number evenly divisible by 7 is encountered.
		Iterator<Integer> inputGenerator = new Iterator<Integer>() {

			Random random = new Random();
			// end::user_guide[]
			{
				// Use fixed seed to always produce the same number of tests for execution on the CI server
				random = new Random(23);
			}
			// tag::user_guide[]
			int current;

			@Override
			public boolean hasNext() {
				current = random.nextInt(100);
				return current % 7 != 0;
			}

			@Override
			public Integer next() {
				return current;
			}
		};

		// Generates display names like: input:5, input:37, input:85, etc.
		Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;

		// Executes tests based on the current input value.
		ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);

		// Returns a stream of dynamic tests.
		return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
	}

	@TestFactory
	Stream<DynamicTest> dynamicTestsFromStreamFactoryMethod() {
		// Stream of palindromes to check
		Stream<String> inputStream = Stream.of("racecar", "radar", "mom", "dad");

		// Generates display names like: racecar is a palindrome
		Function<String, String> displayNameGenerator = text -> text + " is a palindrome";

		// Executes tests based on the current input value.
		ThrowingConsumer<String> testExecutor = text -> assertTrue(isPalindrome(text));

		// Returns a stream of dynamic tests.
		return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor);
	}

	@TestFactory
	Stream<DynamicTest> dynamicTestsFromStreamFactoryMethodWithNames() {
		// Stream of palindromes to check
		Stream<Named<String>> inputStream = Stream.of(
				named("racecar is a palindrome", "racecar"),
				named("radar is also a palindrome", "radar"),
				named("mom also seems to be a palindrome", "mom"),
				named("dad is yet another palindrome", "dad")
			);

		// Returns a stream of dynamic tests.
		return DynamicTest.stream(inputStream,
			text -> assertTrue(isPalindrome(text)));
	}

	@TestFactory
	Stream<DynamicNode> dynamicTestsWithContainers() {
		return Stream.of("A", "B", "C")
			.map(input -> dynamicContainer("Container " + input, Stream.of(
				dynamicTest("not null", () -> assertNotNull(input)),
				dynamicContainer("properties", Stream.of(
					dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
					dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
				))
			)));
	}

	@TestFactory
	DynamicNode dynamicNodeSingleTest() {
		return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));
	}

	@TestFactory
	DynamicNode dynamicNodeSingleContainer() {
		return dynamicContainer("palindromes",
			Stream.of("racecar", "radar", "mom", "dad")
				.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))
		));
	}

}
// end::user_guide[]
