package org.jooby.scanner;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.List;
import javax.inject.Provider;
import org.jooby.Env;
import org.jooby.Jooby;
import org.jooby.Registry;
import org.jooby.Router;
import org.jooby.mvc.Path;
import org.jooby.test.MockUnit;
import org.jooby.test.MockUnit.Block;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.typesafe.config.Config;
import app.ns.AbsController;
import app.ns.AbsFoo;
import app.ns.FooApp;
import app.ns.FooController;
import app.ns.FooImpl;
import app.ns.FooModule;
import app.ns.FooSub;
import app.ns.GuavaService;
import app.ns.GuiceModule;
import app.ns.IFoo;
import app.ns.NamedFoo;
import app.ns.SingletonFoo;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import javaslang.control.Try.CheckedConsumer;
import javaslang.control.Try.CheckedRunnable;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Scanner.class, FastClasspathScanner.class, FooModule.class, ServiceManager.class })
public class ScannerTest {
private Block routes = unit -> {
Env env = unit.get(Env.class);
expect(env.router()).andReturn(unit.get(Router.class));
};
private Block runtimeProcessors = unit -> {
expect(unit.get(Config.class).getInt("runtime.processors")).andReturn(1);
};
@Test
public void newScanner() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void newScannerWithSpec() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(runtimeProcessors)
.expect(scanResult("test.pkg"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.run(unit -> {
new Scanner("test.pkg")
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanController() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class, FooController.class.getName()))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(unit -> {
Router routes = unit.get(Router.class);
expect(routes.use(FooController.class)).andReturn(null);
})
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanModule() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class, FooModule.class.getName()))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
expect(binder.bind(FooModule.class)).andReturn(null);
})
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanApp() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class, FooApp.class.getName()))
.expect(appclass(ScannerTest.class.getName()))
.expect(unit -> {
Router routes = unit.get(Router.class);
expect(routes.use(isA(FooApp.class))).andReturn(routes);
})
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("unchecked")
@Test
public void scanAnnotation() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns", "javax.inject", "com.google.inject.name"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(annotations(javax.inject.Named.class, NamedFoo.class.getName()))
.expect(annotations(com.google.inject.name.Named.class))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
AnnotatedBindingBuilder<NamedFoo> abb = unit.mock(AnnotatedBindingBuilder.class);
abb.asEagerSingleton();
expect(binder.bind(NamedFoo.class)).andReturn(abb);
Env env = unit.get(Env.class);
expect(env.lifeCycle(NamedFoo.class)).andReturn(env);
})
.run(unit -> {
new Scanner()
.scan(javax.inject.Named.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("unchecked")
@Test
public void scanSingleton() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns", "javax.inject", "com.google.inject"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(annotations(javax.inject.Singleton.class, SingletonFoo.class.getName()))
.expect(annotations(com.google.inject.Singleton.class))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
AnnotatedBindingBuilder<SingletonFoo> abb = unit.mock(AnnotatedBindingBuilder.class);
abb.asEagerSingleton();
expect(binder.bind(SingletonFoo.class)).andReturn(abb);
Env env = unit.get(Env.class);
expect(env.lifeCycle(SingletonFoo.class)).andReturn(env);
})
.run(unit -> {
new Scanner()
.scan(javax.inject.Singleton.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("unchecked")
@Test
public void scanImplements() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(implementing(IFoo.class, FooImpl.class.getName()))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
AnnotatedBindingBuilder<FooImpl> abb = unit.mock(AnnotatedBindingBuilder.class);
abb.asEagerSingleton();
expect(binder.bind(FooImpl.class)).andReturn(abb);
Env env = unit.get(Env.class);
expect(env.lifeCycle(FooImpl.class)).andReturn(env);
})
.run(unit -> {
new Scanner()
.scan(IFoo.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("unchecked")
@Test
public void scanSubclass() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(subClassesOf(AbsFoo.class, FooSub.class.getName()))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
AnnotatedBindingBuilder<FooSub> abb = unit.mock(AnnotatedBindingBuilder.class);
abb.asEagerSingleton();
expect(binder.bind(FooSub.class)).andReturn(abb);
Env env = unit.get(Env.class);
expect(env.lifeCycle(FooSub.class)).andReturn(env);
})
.run(unit -> {
new Scanner()
.scan(AbsFoo.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanGuiceModule() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns", "com.google.inject"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(implementing(Module.class, GuiceModule.class.getName()))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
expect(binder.bind(GuiceModule.class)).andReturn(null);
})
.run(unit -> {
new Scanner()
.scan(Module.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("unchecked")
@Test
public void scanGuavaService() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class, Registry.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns", "com.google.common.util.concurrent"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(implementing(Service.class, GuavaService.class.getName()))
.expect(unit -> {
Binder binder = unit.get(Binder.class);
AnnotatedBindingBuilder<GuavaService> abb = unit.mock(AnnotatedBindingBuilder.class);
abb.asEagerSingleton();
expect(binder.bind(GuavaService.class)).andReturn(abb);
AnnotatedBindingBuilder<ServiceManager> abbsm = unit.mock(AnnotatedBindingBuilder.class);
expect(abbsm.toProvider(unit.capture(Provider.class))).andReturn(abbsm);
expect(binder.bind(ServiceManager.class)).andReturn(abbsm);
Env env = unit.get(Env.class);
expect(env.onStart(unit.capture(CheckedConsumer.class))).andReturn(env);
expect(env.onStop(unit.capture(CheckedRunnable.class))).andReturn(env);
})
.expect(unit -> {
GuavaService service = unit.mock(GuavaService.class);
Registry registry = unit.get(Registry.class);
expect(registry.require(GuavaService.class)).andReturn(service);
ServiceManager sm = unit.constructor(ServiceManager.class)
.build(Lists.newArrayList(service));
unit.registerMock(ServiceManager.class, sm);
expect(sm.startAsync()).andReturn(sm);
sm.awaitHealthy();
expect(sm.stopAsync()).andReturn(sm);
sm.awaitStopped();
})
.run(unit -> {
new Scanner()
.scan(Service.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
}, unit -> {
unit.captured(CheckedConsumer.class).iterator().next().accept(unit.get(Registry.class));
assertEquals(unit.get(ServiceManager.class),
unit.captured(Provider.class).iterator().next().get());
unit.captured(CheckedRunnable.class).iterator().next().run();
});
}
@Test
public void scanEmptyGuavaService() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class, Registry.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns", "com.google.common.util.concurrent"))
.expect(routes)
.expect(annotations(Path.class))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(implementing(Service.class))
.run(unit -> {
new Scanner()
.scan(Service.class)
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanShouldCreateJustOneController() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(
annotations(Path.class, FooController.class.getName(), FooController.class.getName()))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.expect(unit -> {
Router routes = unit.get(Router.class);
expect(routes.use(FooController.class)).andReturn(null);
})
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@Test
public void scanShouldIgnoreAbsClasses() throws Exception {
new MockUnit(Env.class, Config.class, Binder.class, Router.class)
.expect(ns("app.ns"))
.expect(runtimeProcessors)
.expect(scanResult("app.ns"))
.expect(routes)
.expect(
annotations(Path.class, AbsController.class.getName()))
.expect(implementing(Jooby.Module.class))
.expect(subClassesOf(Jooby.class))
.expect(appclass(ScannerTest.class.getName()))
.run(unit -> {
new Scanner()
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
});
}
@SuppressWarnings("rawtypes")
private Block annotations(final Class type, final String... names) {
return annotations(type, Arrays.asList(names));
}
@SuppressWarnings("rawtypes")
private Block annotations(final Class type, final List<String> classes) {
return unit -> {
ScanResult result = unit.get(ScanResult.class);
expect(result.getNamesOfClassesWithAnnotation(type)).andReturn(classes);
};
}
@SuppressWarnings("rawtypes")
private Block implementing(final Class type, final String... names) {
return implementing(type, Arrays.asList(names));
}
@SuppressWarnings("rawtypes")
private Block implementing(final Class type, final List<String> classes) {
return unit -> {
ScanResult result = unit.get(ScanResult.class);
expect(result.getNamesOfClassesImplementing(type))
.andReturn(classes);
};
}
@SuppressWarnings("rawtypes")
private Block subClassesOf(final Class type, final String... names) {
return subClassesOf(type, Arrays.asList(names));
}
@SuppressWarnings("rawtypes")
private Block subClassesOf(final Class type, final List<String> classes) {
return unit -> {
ScanResult result = unit.get(ScanResult.class);
expect(result.getNamesOfSubclassesOf(type))
.andReturn(classes);
};
}
private Block ns(final String pkg) {
return unit -> {
Config conf = unit.get(Config.class);
expect(conf.getString("application.ns")).andReturn(pkg);
};
}
private Block appclass(final String main) {
return unit -> {
Config conf = unit.get(Config.class);
expect(conf.getString("application.class")).andReturn(main);
};
}
private Block scanResult(final Object... spec) {
return unit -> {
FastClasspathScanner scanner = unit.constructor(FastClasspathScanner.class)
.args(String[].class)
.build(spec);
ScanResult result = unit.mock(ScanResult.class);
expect(scanner.scan(2)).andReturn(result);
unit.registerMock(ScanResult.class, result);
};
}
}