org.mockito.exceptions.verification.SmartNullPointerException:
You have a NullPointerException here:
-> at com.example.mockito.MockitoReturnsSmartNullsTest.test(MockitoReturnsSmartNullsTest.java:20)
because this method call was *not* stubbed correctly:
-> at com.example.mockito.MockitoReturnsSmartNullsTest.test(MockitoReturnsSmartNullsTest.java:20)
foo.boz();
at com.example.mockito.MockitoReturnsSmartNullsTest.test(MockitoReturnsSmartNullsTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
when で指定されていないメソッドのデフォルトの挙動をグローバルに変更するには org.mockito.configuration.MockitoConfiguration クラスを実装すればよいです。
この名前は org.mockito.internal.configuration.ClassPathLoader にハードコードされてますので、変更は不可能です。
以下のクラスを設置するだけで、利用できるので簡単です。
package org.mockito.configuration;
import org.mockito.stubbing.Answer;
// https://solidsoft.wordpress.com/2012/07/02/beyond-the-mockito-refcard-part-1-a-better-error-message-on-npe-with-globally-configured-smartnull/
// See org.mockito.internal.configuration.ClassPathLoader
public class MockitoConfiguration extends DefaultMockitoConfiguration {
public Answer<Object> getDefaultAnswer() {
return new ReturnsSmartNulls();
}
}
buildscript {
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
apply plugin: 'com.google.protobuf'
protobuf {
protoc {
// The version of protoc must match protobuf-java. If you don't depend on
// protobuf-java directly, you will be transitively depending on the
// protobuf-java version that grpc depends on.
artifact = "com.google.protobuf:protoc:3.0.0"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
idea {
module {
sourceDirs += file("${protobuf.generatedFilesBaseDir}/main/java");
// If you have additional sourceSets and/or codegen plugins, add all of them
sourceDirs += file("${protobuf.generatedFilesBaseDir}/main/grpc");
}
}
dependencies {
["grpc-core", "grpc-stub", "grpc-protobuf"].each { module ->
compile "io.grpc:$module:1.0.0"
}
}
package com.example.grpc;
import io.grpc.stub.StreamObserver;
public class HiService extends HiGrpc.HiImplBase {
@Override
public void hello(MyStringValue request, StreamObserver<MyStringValue> responseObserver) {
responseObserver.onNext(MyStringValue.newBuilder()
.setValue("Hi, " + request.getValue() + "!")
.build());
responseObserver.onCompleted();
}
}
```
最後に armeria サーバーの起動部分だけ実装すれば完成。簡単。
```
public class MyApp {
public static void main(String[] args) {
ServerBuilder sb = new ServerBuilder();
sb.port(8080, SessionProtocol.HTTP);
GrpcService grpcService = new GrpcServiceBuilder()
.addService(new HiService())
.build();
sb.serviceUnder(
"/",
grpcService.decorate(LoggingService::new));
Server server = sb.build();
server.start();
}
}
```
# gRPC 採用した場合のメリット
もともと armeria は thrift 用のサーバーなわけだけど、gRPC を採用した場合には以下のような恩恵を得られます。
* Full support for request/response streaming
* Protocol buffer generated code can be a nice change
* Efficient clients on a variety of platforms (Android, iOS, Go, etc)
一方で、grpc 使うと DocService 使えないとか JSON で利用できないとかそういうデメリットも現状あるようだ
# armeria の上で gRPC 動かした場合のメリット
一方、gRPC 使ってるユーザーが armeria の上で gRPC を動かすメリットとしては以下のようなことがあるとのこと
* Support for HTTP1 (not verified, will probably require some followup work). HTTP1 should open GRPC to the browser and work better with Cloud load balancers that generally translate HTTP2 -> HTTP1
* Once implemented, DocService and Grpc on the same server
* And any other servers they feel like having on the same server since armeria's flexible that way :)
とはいえ、現状だと grpc のクライアントって直接 HTTP で話せるわけでもないし HTTP1 サポートしたいというモチベーションも薄いかなあ。protobuf の方がネックになるし。あと DocService も grpc に対応してない様子。
DocService に対応して、さらに JSON エンドポイントが自動で生えるぐらいまでなったらぜひ使いたい。 https://github.com/grpc-ecosystem/grpc-gateway 的な感じで、` GrpcJsonServiceBuilder.service(myService).build()` とかで JSON API 提供できる、とかだと嬉しい。
# まとめ
今後に期待。
Argument(s) are different! Wanted:
foo.foo(() {hoge=4});
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Actual invocation has different arguments:
foo.foo(() {hoge=4});
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
これが以下のように L suffix がついていれば問題点が歴然となり、血管に優しいのではないか、と考えました。
Argument(s) are different! Wanted:
foo.foo({"hoge"=4L});
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Actual invocation has different arguments:
foo.foo({"hoge"=4});
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
$ ~/Downloads/semaphore_darwin_amd64 -setup
Hello! You will now be guided through a setup to:
1. Set up configuration for a MySQL/MariaDB database
2. Set up a path for your playbooks (auto-created)
3. Run database Migrations
4. Set up initial seamphore user & password
> DB Hostname (default 127.0.0.1:3306):
> DB User (default root):
> DB Password: my-secret-pw
> DB Name (default semaphore):
> Playbook path:
Generated configuration:
{
"mysql": {
"host": "127.0.0.1:3306",
"user": "root",
"pass": "my-secret-pw",
"name": "semaphore"
},
"port": "",
"bugsnag_key": "",
"tmp_path": "/tmp/semaphore",
"cookie_hash": "3aIKJIwVCvXH6vLGb8xhQHMr90OeVZ1SimYMNJBWD+A=",
"cookie_encryption": "TDFElfq2JZN5PfTfPedRjgMZ+MZUY9VgNTZCS/sGJTY="
}
> Is this correct? (yes/no): yes
Running: mkdir -p /tmp/semaphore..
Configuration written to /tmp/semaphore/semaphore_config.json..
Pinging database..
Running DB Migrations..
Creating migrations table
Executing migration v0.0.0 (at 2016-06-25 08:44:13.784368138 +0900 JST)...
[11/11]
Executing migration v1.0.0 (at 2016-06-25 08:44:14.340635482 +0900 JST)...
[7/7]
Executing migration v1.1.0 (at 2016-06-25 08:44:14.752282549 +0900 JST)...
[1/1]
Executing migration v1.2.0 (at 2016-06-25 08:44:14.861796122 +0900 JST)...
[1/1]
Executing migration v1.3.0 (at 2016-06-25 08:44:14.918184638 +0900 JST)...
[3/3]
Executing migration v1.4.0 (at 2016-06-25 08:44:15.103055625 +0900 JST)...
[2/2]
Executing migration v1.5.0 (at 2016-06-25 08:44:15.220915164 +0900 JST)...
[1/1]
Executing migration v0.1.0 (at 2016-06-25 08:44:15.279912624 +0900 JST)...
[6/6]
> Username: foobar
> Email: [email protected]
> Your name: Foo Bar
> Password: *********
You are all setup Foo Bar!
Re-launch this program pointing to the configuration file
./semaphore -config /tmp/semaphore/semaphore_config.json
To run as daemon:
nohup ./semaphore -config /tmp/semaphore/semaphore_config.json &
You can login with [email protected] or foobar.
設定ファイルが生成されるんで、メッセージどおりにそれを使って起動すれば OK.
ここまでは極めて簡単にできる。
現時点の最新版である v2.0.2 には致命的な問題がある(全くデプロイできない)ので、困ったなーって思って git log 見たら HEAD では治ってたので、リリースしてよ、って言ったら音速でリリースされた。
spring boot の起動が極めて遅くて辛いなと感じていたところ、どうやら spring aop が極めて多くの時間を浪費しているということが判明した。
spring aop をオフにすると起動時間が16秒なのに対し、オンにすると 26 秒に増える。しかも、利用しているエンドポイントの数に比例しているようで、これは今後さらに遅くなりそうだ。。
spring security に入っていた aspectj plugin をベースに書いたが、もはや原型はとどめていないコードがこちらになります。
package aspectj
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.TaskAction
import java.nio.file.Path
// taken from spring security.
// https://github.com/spring-projects/spring-security/blob/master/buildSrc/src/main/groovy/aspectj/AspectJPlugin.groovy
/**
*
* @author Luke Taylor
*/
class AspectJPlugin implements Plugin<Project> {
void apply(Project project) {
project.plugins.apply(JavaPlugin)
if (!project.hasProperty('aspectjVersion')) {
throw new GradleException("You must set the property 'aspectjVersion' before applying the aspectj plugin")
}
if (project.configurations.findByName('ajtools') == null) {
project.configurations.create('ajtools')
project.dependencies {
ajtools "org.aspectj:aspectjtools:${project.aspectjVersion}"
compile "org.aspectj:aspectjrt:${project.aspectjVersion}"
}
}
project.afterEvaluate {
project.tasks.create(name: 'compileAspect', overwrite: true, description: 'Compiles AspectJ Source', type: Ajc) {
dependsOn project.processResources, project.compileJava
tmpDir = "${project.buildDir}/aspect/"
args = [
"-inpath", project.sourceSets.main.output.classesDir.toPath(),
"-showWeaveInfo",
"-1.8",
"-d", tmpDir,
"-classpath", project.sourceSets.main.compileClasspath.asPath,
];
dstDir = project.sourceSets.main.output.classesDir.toPath()
}
project.tasks.classes.dependsOn project.tasks.compileAspect
project.tasks.create(name: 'compileTestAspect', overwrite: true, description: 'Compiles AspectJ Test Source', type: Ajc) {
dependsOn project.processTestResources, project.compileTestJava
tmpDir = "${project.buildDir}/test-aspect/"
def classpath = project.sourceSets.test.compileClasspath.files.grep({ it.exists() }).join(":")
args = [
"-inpath", project.sourceSets.test.output.classesDir.toPath(),
"-aspectpath", project.sourceSets.main.output.classesDir.toPath(),
"-aspectpath", project.sourceSets.test.output.classesDir.toPath(),
"-showWeaveInfo",
"-1.8",
"-d", tmpDir,
"-classpath", classpath
];
dstDir = project.sourceSets.test.output.classesDir.toPath()
}
project.tasks.testClasses.dependsOn project.tasks.compileTestAspect
}
}
}
class Ajc extends DefaultTask {
String[] args
String tmpDir
Path dstDir
Ajc() {
logging.captureStandardOutput(LogLevel.INFO)
}
//http://www.eclipse.org/aspectj/doc/released/devguide/ajc-ref.html
// http://stackoverflow.com/questions/3660547/apt-and-aop-in-the-same-project-using-maven
// https://github.com/uPhyca/gradle-android-aspectj-plugin/blob/8d580d8117932a23209421193da77f175d19d416/plugin/src/main/groovy/com/uphyca/gradle/android/AspectjCompile.groovy
@TaskAction
def compile() {
logger.info("Running ajc ...")
MessageHandler handler = new MessageHandler(false);
logger.info("args: $args")
new Main().run(args as String[], handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
logger.error message.message, message.thrown
break;
case IMessage.WARNING:
logger.warn message.message, message.thrown
break;
case IMessage.INFO:
logger.info message.message, message.thrown
break;
case IMessage.DEBUG:
logger.debug message.message, message.thrown
break;
}
}
ant.move(file: tmpDir, tofile: dstDir)
}
}
ネットで情報を gradle+aspectj の例を探していると、aspectj の ant task を利用する方法がいくつか出てくるが、aspectj の ant plugin の記述方法を元に gradle で ant task を呼ぶのは、
デバッグしにくいので難しい。aspectj の Main を呼ぶ方法が便利だと思う。
(もちろん、Main を呼ぶのはdocumentedなインターフェースではないので、壊れる可能性があるが、壊れたら別の Main を呼べばいいだけ)
この Main を直接叩く方法は、gradle の android で aspectj するプラグインを参考にした。
まあそういう感じでできるようになったので良かったね、と。
で、load time weaving についても、こんなに遅いわけもないと思うから、もうちょい頑張れば速くなるのかもしれないけど知見がまったくないです。
$ jspm init
Package.json file does not exist, create it? [yes]:
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]:
Enter server baseURL (public folder path) [./]:
Enter jspm packages folder [./jspm_packages]:
Enter config file path [./config.js]:
Configuration file config.js doesn't exist, create it? [yes]:
Enter client baseURL (public folder URL) [/]:
Do you wish to use a transpiler? [yes]:
Which ES6 transpiler would you like to use, Babel, TypeScript or Traceur? [babel]:typescript
(以下略)
multipart.location specifies the directory where files will be stored. The default is "". A common value is to use the system's temporary directory, which can be obtained.
multipart.maxFileSize specifies the maximum size permitted for uploaded files. The default is 1Mb.
multipart.maxRequestSize specifies the maximum size allowed for multipart/form-data requests. The default is 10Mb
multipart.fileSizeThreshold specifies the size threshold after which files will be written to disk. Default is 0, which means that the file will be written to disk immediately.