Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
wd1105040417
retrofit
提交
4c3c17b2
R
retrofit
项目概览
wd1105040417
/
retrofit
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
retrofit
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
4c3c17b2
编写于
12月 16, 2019
作者:
J
Jake Wharton
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add coroutine support to BehaviorDelegate
(cherry picked from commit
1ec98c23
)
上级
62ab32f3
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
313 addition
and
3 deletion
+313
-3
retrofit-mock/pom.xml
retrofit-mock/pom.xml
+30
-0
retrofit-mock/src/main/java/retrofit2/mock/BehaviorDelegate.java
...t-mock/src/main/java/retrofit2/mock/BehaviorDelegate.java
+99
-3
retrofit-mock/src/main/java/retrofit2/mock/KotlinExtensions.kt
...fit-mock/src/main/java/retrofit2/mock/KotlinExtensions.kt
+18
-0
retrofit-mock/src/test/java/retrofit2/mock/BehaviorDelegateKotlinTest.kt
...rc/test/java/retrofit2/mock/BehaviorDelegateKotlinTest.kt
+166
-0
未找到文件。
retrofit-mock/pom.xml
浏览文件 @
4c3c17b2
...
...
@@ -24,6 +24,11 @@
<artifactId>
jsr305
</artifactId>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.jetbrains.kotlin
</groupId>
<artifactId>
kotlin-stdlib
</artifactId>
<optional>
true
</optional>
</dependency>
<dependency>
<groupId>
junit
</groupId>
...
...
@@ -35,10 +40,35 @@
<artifactId>
assertj-core
</artifactId>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.jetbrains.kotlinx
</groupId>
<artifactId>
kotlinx-coroutines-core
</artifactId>
<scope>
test
</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.jetbrains.kotlin
</groupId>
<artifactId>
kotlin-maven-plugin
</artifactId>
<executions>
<execution>
<id>
compile
</id>
<phase>
process-sources
</phase>
<goals>
<goal>
compile
</goal>
</goals>
</execution>
<execution>
<id>
test-compile
</id>
<phase>
test-compile
</phase>
<goals>
<goal>
test-compile
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-jar-plugin
</artifactId>
...
...
retrofit-mock/src/main/java/retrofit2/mock/BehaviorDelegate.java
浏览文件 @
4c3c17b2
...
...
@@ -18,12 +18,17 @@ package retrofit2.mock;
import
java.lang.annotation.Annotation
;
import
java.lang.reflect.InvocationHandler
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Proxy
;
import
java.lang.reflect.Type
;
import
java.lang.reflect.WildcardType
;
import
java.util.concurrent.ExecutorService
;
import
javax.annotation.Nullable
;
import
kotlin.coroutines.Continuation
;
import
retrofit2.Call
;
import
retrofit2.CallAdapter
;
import
retrofit2.KotlinExtensions
;
import
retrofit2.Response
;
import
retrofit2.Retrofit
;
/**
...
...
@@ -58,12 +63,103 @@ public final class BehaviorDelegate<T> {
new
InvocationHandler
()
{
@Override
public
T
invoke
(
Object
proxy
,
Method
method
,
Object
[]
args
)
throws
Throwable
{
Type
returnType
=
method
.
getGenericReturnType
();
ServiceMethodAdapterInfo
adapterInfo
=
parseServiceMethodAdapterInfo
(
method
);
Annotation
[]
methodAnnotations
=
method
.
getAnnotations
();
CallAdapter
<
R
,
T
>
callAdapter
=
(
CallAdapter
<
R
,
T
>)
retrofit
.
callAdapter
(
returnType
,
methodAnnotations
);
return
callAdapter
.
adapt
(
behaviorCall
);
(
CallAdapter
<
R
,
T
>)
retrofit
.
callAdapter
(
adapterInfo
.
responseType
,
methodAnnotations
);
T
adapted
=
callAdapter
.
adapt
(
behaviorCall
);
if
(!
adapterInfo
.
isSuspend
)
{
return
adapted
;
}
Call
<
Object
>
adaptedCall
=
(
Call
<
Object
>)
adapted
;
Continuation
<
Object
>
continuation
=
(
Continuation
<
Object
>)
args
[
args
.
length
-
1
];
try
{
return
adapterInfo
.
wantsResponse
?
(
T
)
KotlinExtensions
.
awaitResponse
(
adaptedCall
,
continuation
)
:
(
T
)
KotlinExtensions
.
await
(
adaptedCall
,
continuation
);
}
catch
(
Exception
e
)
{
return
(
T
)
KotlinExtensions
.
suspendAndThrow
(
e
,
continuation
);
}
}
});
}
/**
* Computes the adapter type of the method for lookup via {@link Retrofit#callAdapter} as well as
* information on whether the method is a {@code suspend fun}.
* <p>
* In the case of a Kotlin {@code suspend fun}, the last parameter type is a {@code Continuation}
* whose parameter carries the actual response type. In this case, we return {@code Call<T>} where
* {@code T} is the body type.
*/
private
static
ServiceMethodAdapterInfo
parseServiceMethodAdapterInfo
(
Method
method
)
{
Type
[]
genericParameterTypes
=
method
.
getGenericParameterTypes
();
if
(
genericParameterTypes
.
length
!=
0
)
{
Type
lastParameterType
=
genericParameterTypes
[
genericParameterTypes
.
length
-
1
];
if
(
lastParameterType
instanceof
ParameterizedType
)
{
ParameterizedType
parameterizedLastParameterType
=
(
ParameterizedType
)
lastParameterType
;
try
{
if
(
parameterizedLastParameterType
.
getRawType
()
==
Continuation
.
class
)
{
Type
resultType
=
parameterizedLastParameterType
.
getActualTypeArguments
()[
0
];
if
(
resultType
instanceof
WildcardType
)
{
resultType
=
((
WildcardType
)
resultType
).
getLowerBounds
()[
0
];
}
if
(
resultType
instanceof
ParameterizedType
)
{
ParameterizedType
parameterizedResultType
=
(
ParameterizedType
)
resultType
;
if
(
parameterizedResultType
.
getRawType
()
==
Response
.
class
)
{
Type
bodyType
=
parameterizedResultType
.
getActualTypeArguments
()[
0
];
Type
callType
=
new
CallParameterizedTypeImpl
(
bodyType
);
return
new
ServiceMethodAdapterInfo
(
true
,
true
,
callType
);
}
}
Type
callType
=
new
CallParameterizedTypeImpl
(
resultType
);
return
new
ServiceMethodAdapterInfo
(
true
,
false
,
callType
);
}
}
catch
(
NoClassDefFoundError
ignored
)
{
// Not using coroutines.
}
}
}
return
new
ServiceMethodAdapterInfo
(
false
,
false
,
method
.
getGenericReturnType
());
}
static
final
class
CallParameterizedTypeImpl
implements
ParameterizedType
{
private
final
Type
bodyType
;
CallParameterizedTypeImpl
(
Type
bodyType
)
{
this
.
bodyType
=
bodyType
;
}
@Override
public
Type
[]
getActualTypeArguments
()
{
return
new
Type
[]
{
bodyType
};
}
@Override
public
Type
getRawType
()
{
return
Call
.
class
;
}
@Override
public
@Nullable
Type
getOwnerType
()
{
return
null
;
}
}
static
class
ServiceMethodAdapterInfo
{
final
boolean
isSuspend
;
/**
* Whether the suspend function return type was {@code Response<T>}.
* Only meaningful if {@link #isSuspend} is true.
*/
final
boolean
wantsResponse
;
final
Type
responseType
;
ServiceMethodAdapterInfo
(
boolean
isSuspend
,
boolean
wantsResponse
,
Type
responseType
)
{
this
.
isSuspend
=
isSuspend
;
this
.
wantsResponse
=
wantsResponse
;
this
.
responseType
=
responseType
;
}
}
}
retrofit-mock/src/main/java/retrofit2/mock/KotlinExtensions.kt
0 → 100644
浏览文件 @
4c3c17b2
/*
* Copyright (C) 2019 Square, Inc.
*
* 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
retrofit2.mock
inline
fun
<
reified
T
>
MockRetrofit
.
create
():
BehaviorDelegate
<
T
>
=
create
(
T
::
class
.
java
)
retrofit-mock/src/test/java/retrofit2/mock/BehaviorDelegateKotlinTest.kt
0 → 100644
浏览文件 @
4c3c17b2
/*
* Copyright (C) 2019 Square, Inc.
*
* 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
retrofit2.mock
import
kotlinx.coroutines.runBlocking
import
org.assertj.core.api.Assertions.assertThat
import
org.junit.Before
import
org.junit.Test
import
retrofit2.Response
import
retrofit2.Retrofit
import
java.io.IOException
import
java.util.Random
import
java.util.concurrent.TimeUnit.MILLISECONDS
import
java.util.concurrent.TimeUnit.NANOSECONDS
class
BehaviorDelegateKotlinTest
{
internal
interface
DoWorkService
{
suspend
fun
body
():
String
suspend
fun
failure
():
String
suspend
fun
response
():
Response
<
String
>
suspend
fun
responseWildcard
():
Response
<
out
String
>
}
private
val
mockFailure
=
IOException
(
"Timeout!"
)
private
val
behavior
=
NetworkBehavior
.
create
(
Random
(
2847
))
private
lateinit
var
service
:
DoWorkService
@Before
fun
before
()
{
val
retrofit
=
Retrofit
.
Builder
()
.
baseUrl
(
"http://example.com"
)
.
build
()
val
mockRetrofit
=
MockRetrofit
.
Builder
(
retrofit
)
.
networkBehavior
(
behavior
)
.
build
()
val
delegate
=
mockRetrofit
.
create
<
DoWorkService
>()
service
=
object
:
DoWorkService
{
override
suspend
fun
body
():
String
{
return
delegate
.
returning
(
Calls
.
response
(
"Response!"
)).
body
()
}
override
suspend
fun
failure
():
String
{
val
failure
=
Calls
.
failure
<
String
>(
mockFailure
)
return
delegate
.
returning
(
failure
).
failure
()
}
override
suspend
fun
response
():
Response
<
String
>
{
val
response
=
Calls
.
response
(
"Response!"
)
return
delegate
.
returning
(
response
).
response
()
}
override
suspend
fun
responseWildcard
()
=
response
()
}
}
@Test
fun
body
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
0
)
val
startNanos
=
System
.
nanoTime
()
val
result
=
runBlocking
{
service
.
body
()
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
assertThat
(
result
).
isEqualTo
(
"Response!"
)
}
@Test
fun
bodyFailure
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
100
)
val
startNanos
=
System
.
nanoTime
()
val
exception
=
runBlocking
{
try
{
throw
AssertionError
(
service
.
body
())
}
catch
(
e
:
Exception
)
{
e
}
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
assertThat
(
exception
).
isSameAs
(
behavior
.
failureException
())
}
@Test
fun
failure
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
0
)
val
startNanos
=
System
.
nanoTime
()
val
exception
=
runBlocking
{
try
{
throw
AssertionError
(
service
.
failure
())
}
catch
(
e
:
Exception
)
{
e
}
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
// Coroutines break referential transparency on exceptions so compare type and message.
assertThat
(
exception
).
isExactlyInstanceOf
(
mockFailure
.
javaClass
)
assertThat
(
exception
).
hasMessage
(
mockFailure
.
message
)
}
@Test
fun
response
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
0
)
val
startNanos
=
System
.
nanoTime
()
val
result
=
runBlocking
{
service
.
response
()
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
assertThat
(
result
.
body
()).
isEqualTo
(
"Response!"
)
}
@Test
fun
responseFailure
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
100
)
val
startNanos
=
System
.
nanoTime
()
val
exception
=
runBlocking
{
try
{
throw
AssertionError
(
service
.
response
())
}
catch
(
e
:
Exception
)
{
e
}
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
assertThat
(
exception
).
isSameAs
(
behavior
.
failureException
())
}
@Test
fun
responseWildcard
()
{
behavior
.
setDelay
(
100
,
MILLISECONDS
)
behavior
.
setVariancePercent
(
0
)
behavior
.
setFailurePercent
(
0
)
val
startNanos
=
System
.
nanoTime
()
val
result
=
runBlocking
{
service
.
responseWildcard
()
}
val
tookMs
=
NANOSECONDS
.
toMillis
(
System
.
nanoTime
()
-
startNanos
)
assertThat
(
tookMs
).
isGreaterThanOrEqualTo
(
100
)
assertThat
(
result
.
body
()).
isEqualTo
(
"Response!"
)
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录