/*
 * Copyright 2015-2017 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
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.engine.descriptor;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;
import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME;
import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getDefaultTestInstanceLifecycle;
import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

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

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.platform.commons.util.PreconditionViolationException;
import org.junit.platform.engine.ConfigurationParameters;

/**
 * Unit tests for {@link TestInstanceLifecycleUtils}.
 *
 * <p>NOTE: it doesn't make sense to unit test the JVM system property fallback
 * support in this test class since that feature is a concrete implementation
 * detail of {@code LauncherConfigurationParameters} which necessitates an
 * integration test via the {@code Launcher} API.
 *
 * @since 5.0
 */
class TestInstanceLifecycleUtilsTests {

	private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME;

	@Test
	void getDefaultTestInstanceLifecyclePreconditions() {
		PreconditionViolationException exception = assertThrows(PreconditionViolationException.class,
			() -> getDefaultTestInstanceLifecycle(null));
		assertThat(exception).hasMessage("ConfigurationParameters must not be null");
	}

	@Test
	void getDefaultTestInstanceLifecycleWithNoConfigParamSet() {
		Lifecycle lifecycle = getDefaultTestInstanceLifecycle(mock(ConfigurationParameters.class));
		assertThat(lifecycle).isEqualTo(PER_METHOD);
	}

	@Test
	void getDefaultTestInstanceLifecycleWithConfigParamSet() {
		assertAll(//
			() -> assertDefaultConfigParam(null, PER_METHOD), //
			() -> assertDefaultConfigParam("", PER_METHOD), //
			() -> assertDefaultConfigParam("bogus", PER_METHOD), //
			() -> assertDefaultConfigParam(PER_METHOD.name(), PER_METHOD), //
			() -> assertDefaultConfigParam(PER_METHOD.name().toLowerCase(), PER_METHOD), //
			() -> assertDefaultConfigParam("  " + PER_METHOD.name() + "  ", PER_METHOD), //
			() -> assertDefaultConfigParam(PER_CLASS.name(), PER_CLASS), //
			() -> assertDefaultConfigParam(PER_CLASS.name().toLowerCase(), PER_CLASS), //
			() -> assertDefaultConfigParam("  " + PER_CLASS.name() + "  ", Lifecycle.PER_CLASS) //
		);
	}

	private void assertDefaultConfigParam(String configValue, Lifecycle expected) {
		ConfigurationParameters configParams = mock(ConfigurationParameters.class);
		when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue));
		Lifecycle lifecycle = getDefaultTestInstanceLifecycle(configParams);
		assertThat(lifecycle).isEqualTo(expected);
	}

	@Test
	void getTestInstanceLifecyclePreconditions() {
		PreconditionViolationException exception = assertThrows(PreconditionViolationException.class,
			() -> getTestInstanceLifecycle(null, mock(ConfigurationParameters.class)));
		assertThat(exception).hasMessage("testClass must not be null");

		exception = assertThrows(PreconditionViolationException.class,
			() -> getTestInstanceLifecycle(getClass(), null));
		assertThat(exception).hasMessage("ConfigurationParameters must not be null");
	}

	@Test
	void getTestInstanceLifecycleWithNoConfigParamSet() {
		Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), mock(ConfigurationParameters.class));
		assertThat(lifecycle).isEqualTo(PER_METHOD);
	}

	@Test
	void getTestInstanceLifecycleWithConfigParamSet() {
		ConfigurationParameters configParams = mock(ConfigurationParameters.class);
		when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase()));
		Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), configParams);
		assertThat(lifecycle).isEqualTo(PER_CLASS);
	}

	@Test
	void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() {
		ConfigurationParameters configParams = mock(ConfigurationParameters.class);
		when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase()));
		Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, configParams);
		assertThat(lifecycle).isEqualTo(PER_METHOD);
	}

	@Test
	void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() {
		Class<?> testClass = MetaAnnotationTestCase.class;
		Lifecycle lifecycle = getTestInstanceLifecycle(testClass, mock(ConfigurationParameters.class));
		assertThat(lifecycle).isEqualTo(PER_CLASS);
	}

	@TestInstance(Lifecycle.PER_METHOD)
	private static class TestCase {
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	@TestInstance(Lifecycle.PER_CLASS)
	private @interface PerClassLifeCycle {
	}

	@PerClassLifeCycle
	private static class MetaAnnotationTestCase {
	}

}

@@ -1102,16 +1102,55 @@
 ntType;%0A
+import java.lang.annotation.Inherited;%0A
 import j
@@ -5194,16 +5194,20 @@
 Class = 
+Base
 MetaAnno
@@ -5201,35 +5201,314 @@
  BaseMetaAnnotat
-ion
+edTestCase.class;%0A%09%09Lifecycle lifecycle = getTestInstanceLifecycle(testClass, mock(ConfigurationParameters.class));%0A%09%09assertThat(lifecycle).isEqualTo(PER_CLASS);%0A%09%7D%0A%0A%09@Test%0A%09void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() %7B%0A%09%09Class%3C?%3E testClass = Specialized
 TestCase.class;%0A
@@ -5729,16 +5729,28 @@
 e %7B%0A%09%7D%0A%0A
+%09@Inherited%0A
 %09@Retent
@@ -5927,16 +5927,20 @@
 c class 
+Base
 MetaAnno
@@ -5942,19 +5942,100 @@
 aAnnotat
-ion
+edTestCase %7B%0A%09%7D%0A%0A%09private static class SpecializedTestCase extends BaseMetaAnnotated
 TestCase

Add test for an inherited meta-annotation