/* * Copyright 2011 Atteo. * * 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 org.atteo.moonshine; import java.io.File; import java.io.IOException; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.BDDAssertions.then; import com.google.inject.Binder; import com.google.inject.Module; import com.googlecode.catchexception.apis.BDDCatchException; import org.atteo.filtering.PropertyFilter; import org.atteo.filtering.PropertyNotFoundException; import org.atteo.filtering.PropertyResolver; import org.atteo.moonshine.services.LifeCycleListener; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.beust.jcommander.Parameter; import com.google.inject.AbstractModule; import com.google.inject.CreationException; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.name.Names; import static com.googlecode.catchexception.CatchException.caughtException; import static com.googlecode.catchexception.apis.BDDCatchException.when; public class MoonshineTest { @Test public void shouldStartWithDefaults() throws MoonshineException, IOException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home/") .build()) { moonshine.start(); } } @Test public void shouldStartWithTrivialConfiguration() throws MoonshineException, IOException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home/") .addConfigurationFromString("" + "<config>" + "</config>") .build()) { moonshine.start(); } } @Test public void shouldThrowWhenSingletonWithId() throws MoonshineException, IOException { // given try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home/") .addConfigurationFromString("" + "<config>" + " <singletonService id='test'/>" + "</config>")) .build()) { // then BDDCatchException.then(caughtException()).isInstanceOf(MoonshineException.class) .hasMessage("Service '\"test\" SingletonService' is marked as singleton, but has an id specified"); } } @Test public void shouldThrowWhenMultipleSingletonServices() throws MoonshineException, IOException { // given try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home/") .addConfigurationFromString("" + "<config>" + " <singletonService/>" + " <singletonService/>" + "</config>")) .build()) { BDDCatchException.then(caughtException()).isInstanceOf(MoonshineException.class) .hasMessageContaining("Service 'SingletonService' is marked as singleton," + " but is declared more than once in configuration file"); } } @Test public void shouldAllowMultipleNonSingletonServices() throws MoonshineException, IOException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home/") .addConfigurationFromString("" + "<config>" + " <head id='first'/>" + " <head id='second'/>" + "</config>") .build()) { // then } } @Test public void shouldAllowSingletonAndNonSingletonServicesToCoexist() throws MoonshineException, IOException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home/") .addConfigurationFromString("" + "<config>" + " <head/>" + " <singletonService/>" + "</config>") .build()) { // then } } @Test public void shouldImportService() throws MoonshineException, IOException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromResource("/robot-service.xml") .build()) { moonshine.start(); // when Robot robot = moonshine.getGlobalInjector().getInstance(Robot.class); // then assertThat(robot).isNotNull(); assertThat(robot.getLeftLeg()).isNotNull(); } } @Test public void shouldUseCustomModule() throws MoonshineException, IOException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addModule(new AbstractModule() { @Override protected void configure() { bind(Leg.class); } }) .build()) { moonshine.start(); // when Leg leg = moonshine.getGlobalInjector().getInstance(Leg.class); // then assertThat(leg).isNotNull(); } } @Test public void shouldInjectMembers() throws MoonshineException, IOException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <injectmembers>" + " <subservice/>" + " </injectmembers>" + "</config>") .addModule(binder -> binder.bind(String.class).annotatedWith(Names.named("message")) .toInstance("Message")) .build()) { moonshine.start(); // when String message = moonshine.getGlobalInjector().getInstance( Key.get(String.class, Names.named("injected message"))); // then assertThat(message).isNotNull(); } } @Test public void shouldInjectCustomProperty() throws IOException, MoonshineException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <assertions>" + " <equals>" + " <expected>value</expected>" + " <actual>${property}</actual>" + " </equals>" + " </assertions>" + "</config>") .addPropertyResolver((String property, PropertyFilter filter) -> { if ("property".equals(property)) { return "value"; } throw new PropertyNotFoundException(property); }) .build()) { moonshine.start(); } } @Test public void shouldInjectProperties() throws IOException, MoonshineException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <assertions>" + " <contains>" + " <expected>config</expected>" + " <actual>${configHome}</actual>" + " </contains>" + " <contains>" + " <expected>data</expected>" + " <actual>${dataHome}</actual>" + " </contains>" + " <contains>" + " <expected>cache</expected>" + " <actual>${cacheHome}</actual>" + " </contains>" + " </assertions>" + "</config>") .build()) { moonshine.start(); } } @Test public void shouldEnableInfo() throws IOException, MoonshineException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .arguments(new String[] { "--loglevel", "INFO" }) .build()) { // given moonshine.start(); // when Logger logger = LoggerFactory.getLogger("test"); // then assertThat(logger.isInfoEnabled()).isEqualTo(true); assertThat(logger.isDebugEnabled()).isEqualTo(false); } } @Test public void shouldEnableDebugLogging() throws IOException, MoonshineException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .arguments(new String[] { "--loglevel", "DEBUG" }) .build()) { // given moonshine.start(); // when Logger logger = LoggerFactory.getLogger("test"); // then assertThat(logger.isInfoEnabled()).isEqualTo(true); assertThat(logger.isDebugEnabled()).isEqualTo(true); } } private static class CustomParameters implements ParameterProcessor { @Parameter(names = "--custom") public Boolean custom = false; @Override public void configure(Moonshine.RestrictedBuilder builder) { assertThat(builder).isNotNull(); } } @Test public void shouldUseCustomCommandLineObject() throws MoonshineException, IOException { CustomParameters custom = new CustomParameters(); try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .addParameterProcessor(custom) .arguments(new String[] { "--custom" }) .build()) { // then assertThat(custom.custom).isEqualTo(true); } } @Test public void shouldExitAfterPrintingHelp() throws MoonshineException, IOException { try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .arguments(new String[] { "--help" }) .build()) { // then assertThat(moonshine).isNull(); } } @Test public void shouldDetectNonSingletonServicesBindingWithAnnotation() throws MoonshineException, IOException { try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <incorrect/>" + "</config>")) .build()) { } BDDCatchException.then(caughtException()).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only services marked with @Singleton can bind with annotation"); } @Test public void shouldDetectNonSingletonServicesBindingWithAnnotationInPrivateModule() throws MoonshineException, IOException { try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <incorrect-private/>" + "</config>")) .build()) { } BDDCatchException.then(caughtException()).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only services marked with @Singleton can expose bindings with annotation"); } @Test public void shouldExecuteDeconfigureEvenWhenInjectorCreationFails() throws MoonshineException, IOException { try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <missing-dependency-service/>" + "</config>")) .build()) { } BDDCatchException.then(caughtException()).isInstanceOf(CreationException.class) .hasMessageContaining("Explicit bindings are required and java.lang.String is not explicitly bound."); assertThat(MissingDependencyService.configureCount).isEqualTo(1); assertThat(MissingDependencyService.closeCount).isEqualTo(1); } @Test public void shouldFireListeners() throws MoonshineException, IOException { LifeCycleListener listener = Mockito.mock(LifeCycleListener.class); try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home/") .registerListener(listener) .build()) { Mockito.verify(listener).configured(moonshine.getGlobalInjector()); moonshine.start(); Mockito.verify(listener).started(); } Mockito.verify(listener).stopping(); Mockito.verify(listener).closing(); } @Test public void shouldAutoConfigure() throws MoonshineException, IOException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .autoConfiguration() .homeDirectory("target/test-home/") .build()) { // when Head instance = moonshine.getGlobalInjector().getInstance(Head.class); // then assertThat(instance).isNotNull(); assertThat(instance.getName()).isEqualTo("default-name"); } } @Test public void shouldDetectCyclicDependencies() throws MoonshineException, IOException { try (Moonshine moonshine = when(Moonshine.Factory.builder() .homeDirectory("target/test-home") .addConfigurationFromString("" + "<config>" + " <cyclic-service-a />" + " <cyclic-service-b />" + "</config>")) .build()) { } // then BDDCatchException.then(caughtException()).isInstanceOf(RuntimeException.class) .hasMessage("Service CyclicServiceA depends on itself: CyclicServiceA -> CyclicServiceB -> CyclicServiceA"); } @Test public void shouldProvidePropertyFilter() throws MoonshineException, IOException, PropertyNotFoundException { // given try (Moonshine moonshine = Moonshine.Factory.builder() .homeDirectory("target/test-home") .build()) { // when Injector injector = moonshine.getGlobalInjector(); PropertyFilter filter = injector.getInstance(Key.get(PropertyFilter.class, ApplicationProperties.class)); // then assertThat(filter).isNotNull(); assertThat(filter.getProperty("dataHome")); // throws exception when the test fails } } @Test public void shouldStartMultipleInstancesInOneVM() throws MoonshineException, IOException, InterruptedException { // given class MoonshineStarter extends Thread { private String name; public MoonshineStarter(String name) { this.name = name; } @Override public void run() { try (Moonshine moonshine = Moonshine.Factory.builder() .applicationName("moonshine-" + name) .homeDirectory("target/test-home-" + name) .build()) { moonshine.start(); } catch (MoonshineException | IOException e) { throw new RuntimeException(e); } } } MoonshineStarter s1 = new MoonshineStarter("1"); MoonshineStarter s2 = new MoonshineStarter("2"); // when s1.start(); s2.start(); s1.join(); s2.join(); // then File log1 = new File("target/test-home-1/logs/moonshine-1.log"); File log2 = new File("target/test-home-2/logs/moonshine-2.log"); assertThat(log1.length()).isEqualTo(log2.length()); } }