Branched value refactoring, And/Or support
This commit is contained in:
+65
-42
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.stackvalue
|
package org.jetbrains.kotlin.codegen
|
||||||
|
|
||||||
import com.intellij.psi.tree.IElementType
|
import com.intellij.psi.tree.IElementType
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
@@ -32,7 +32,7 @@ open class BranchedValue(val arg1: StackValue, val arg2: StackValue? = null, val
|
|||||||
|
|
||||||
override fun putSelector(type: Type, v: InstructionAdapter) {
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
val branchJumpLabel = Label()
|
val branchJumpLabel = Label()
|
||||||
condJump(branchJumpLabel, v)
|
condJump(branchJumpLabel, v, true)
|
||||||
val endLabel = Label()
|
val endLabel = Label()
|
||||||
v.iconst(1)
|
v.iconst(1)
|
||||||
v.visitJumpInsn(GOTO, endLabel)
|
v.visitJumpInsn(GOTO, endLabel)
|
||||||
@@ -42,14 +42,10 @@ open class BranchedValue(val arg1: StackValue, val arg2: StackValue? = null, val
|
|||||||
coerceTo(type, v);
|
coerceTo(type, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun condJump(jumpIfFalse: Label, v: InstructionAdapter) {
|
open fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
arg1.put(operandType, v)
|
if (arg1 is CondJump) arg1.condJump(jumpLabel, v, jumpIfFalse) else arg1.put(operandType, v)
|
||||||
arg2?.put(operandType, v)
|
arg2?.put(operandType, v)
|
||||||
v.visitJumpInsn(patchOpcode(opcode, v), jumpIfFalse);
|
v.visitJumpInsn(patchOpcode(if (jumpIfFalse) opcode else negatedOperations[opcode], v), jumpLabel);
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun invert(): BranchedValue {
|
|
||||||
return BranchedValue(this, negatedOperations.get(opcode))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
protected open fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
||||||
@@ -60,12 +56,11 @@ open class BranchedValue(val arg1: StackValue, val arg2: StackValue? = null, val
|
|||||||
val negatedOperations = hashMapOf<Int, Int>()
|
val negatedOperations = hashMapOf<Int, Int>()
|
||||||
|
|
||||||
val TRUE: BranchedValue = object : BranchedValue(StackValue.Constant(true, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
val TRUE: BranchedValue = object : BranchedValue(StackValue.Constant(true, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
override fun invert(): BranchedValue {
|
|
||||||
return FALSE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun condJump(jumpIfFalse: Label, v: InstructionAdapter) {
|
|
||||||
|
|
||||||
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
if (!jumpIfFalse) {
|
||||||
|
v.goTo(jumpLabel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putSelector(type: Type, v: InstructionAdapter) {
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
@@ -73,13 +68,12 @@ open class BranchedValue(val arg1: StackValue, val arg2: StackValue? = null, val
|
|||||||
coerceTo(type, v);
|
coerceTo(type, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val FALSE: BranchedValue = object : BranchedValue(StackValue.Constant(false, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
|
||||||
override fun invert(): BranchedValue {
|
|
||||||
return TRUE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun condJump(jumpIfFalse: Label, v: InstructionAdapter) {
|
val FALSE: BranchedValue = object : BranchedValue(StackValue.Constant(false, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
v.goTo(jumpIfFalse)
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
if (jumpIfFalse) {
|
||||||
|
v.goTo(jumpLabel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putSelector(type: Type, v: InstructionAdapter) {
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
@@ -110,40 +104,73 @@ open class BranchedValue(val arg1: StackValue, val arg2: StackValue? = null, val
|
|||||||
if (argument.type != Type.BOOLEAN_TYPE) {
|
if (argument.type != Type.BOOLEAN_TYPE) {
|
||||||
throw UnsupportedOperationException("operand of ! must be boolean")
|
throw UnsupportedOperationException("operand of ! must be boolean")
|
||||||
}
|
}
|
||||||
|
return Invert(condJump(argument))
|
||||||
if (argument is BranchedValue) {
|
|
||||||
return argument.invert()
|
|
||||||
}
|
|
||||||
return BranchedValue(argument, null, Type.BOOLEAN_TYPE, IFNE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun condJump(condition: StackValue, label: Label, jumpIfFalse: Boolean, iv: InstructionAdapter) {
|
fun condJump(condition: StackValue, label: Label, jumpIfFalse: Boolean, iv: InstructionAdapter) {
|
||||||
var condJump: BranchedValue = if (condition is BranchedValue) {
|
condJump(condition).condJump(label, iv, jumpIfFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun condJump(condition: StackValue): CondJump {
|
||||||
|
return CondJump(if (condition is BranchedValue) {
|
||||||
condition
|
condition
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
BranchedValue(condition, null, Type.BOOLEAN_TYPE, IFEQ)
|
BranchedValue(condition, null, Type.BOOLEAN_TYPE, IFEQ)
|
||||||
}
|
}, IFEQ)
|
||||||
condJump = if (jumpIfFalse) condJump else condJump.invert()
|
|
||||||
condJump.condJump(label, iv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun cmp(opToken: IElementType, operandType: Type, left: StackValue, right: StackValue): StackValue =
|
public fun cmp(opToken: IElementType, operandType: Type, left: StackValue, right: StackValue): StackValue =
|
||||||
if (operandType.getSort() == Type.OBJECT)
|
if (operandType.getSort() == Type.OBJECT)
|
||||||
ObjectCompare(opToken, ObjectCompare.getObjectCompareOpcode(opToken), operandType, left, right)
|
ObjectCompare(opToken, operandType, left, right)
|
||||||
else
|
else
|
||||||
NumberCompare(opToken, NumberCompare.getNumberCompareOpcode(opToken), operandType, left, right)
|
NumberCompare(opToken, operandType, left, right)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class And(arg1: StackValue, arg2: StackValue) :
|
||||||
|
BranchedValue(BranchedValue.condJump(arg1), BranchedValue.condJump(arg2), Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
|
|
||||||
class NumberCompare(val opToken: IElementType, opcode: Int, operandType: Type, left: StackValue, right: StackValue) :
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
BranchedValue(left, right, operandType, opcode) {
|
val stayLabel = Label()
|
||||||
|
(arg1 as CondJump).condJump(if (jumpIfFalse) jumpLabel else stayLabel, v, true)
|
||||||
override fun invert(): BranchedValue {
|
(arg2 as CondJump).condJump(jumpLabel, v, jumpIfFalse)
|
||||||
return NumberCompare(opToken, BranchedValue.negatedOperations[opcode], operandType, arg1, arg2!!)
|
v.visitLabel(stayLabel)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Or(arg1: StackValue, arg2: StackValue) :
|
||||||
|
BranchedValue(BranchedValue.condJump(arg1), BranchedValue.condJump(arg2), Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
|
|
||||||
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
val stayLabel = Label()
|
||||||
|
(arg1 as CondJump).condJump(if (jumpIfFalse) stayLabel else jumpLabel, v, false)
|
||||||
|
(arg2 as CondJump).condJump(jumpLabel, v, jumpIfFalse)
|
||||||
|
v.visitLabel(stayLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Invert(val condition: BranchedValue) : BranchedValue(condition, null, Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
|
|
||||||
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
condition.condJump(jumpLabel, v, !jumpIfFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CondJump(val condition: BranchedValue, op: Int) : BranchedValue(condition, null, Type.BOOLEAN_TYPE, op) {
|
||||||
|
|
||||||
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
|
throw UnsupportedOperationException("Use condJump instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
condition.condJump(jumpLabel, v, jumpIfFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberCompare(val opToken: IElementType, operandType: Type, left: StackValue, right: StackValue) :
|
||||||
|
BranchedValue(left, right, operandType, NumberCompare.getNumberCompareOpcode(opToken)) {
|
||||||
|
|
||||||
override fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
override fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
||||||
when (operandType) {
|
when (operandType) {
|
||||||
@@ -182,12 +209,8 @@ class NumberCompare(val opToken: IElementType, opcode: Int, operandType: Type, l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectCompare(val opToken: IElementType, opcode: Int, operandType: Type, left: StackValue, right: StackValue) :
|
class ObjectCompare(val opToken: IElementType, operandType: Type, left: StackValue, right: StackValue) :
|
||||||
BranchedValue(left, right, operandType, opcode) {
|
BranchedValue(left, right, operandType, ObjectCompare.getObjectCompareOpcode(opToken)) {
|
||||||
|
|
||||||
override fun invert(): BranchedValue {
|
|
||||||
return ObjectCompare(opToken, BranchedValue.negatedOperations[opcode], operandType, arg1, arg2!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getObjectCompareOpcode(opToken: IElementType): Int {
|
fun getObjectCompareOpcode(opToken: IElementType): Int {
|
||||||
@@ -38,7 +38,6 @@ import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension;
|
|||||||
import org.jetbrains.kotlin.codegen.inline.*;
|
import org.jetbrains.kotlin.codegen.inline.*;
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.*;
|
import org.jetbrains.kotlin.codegen.intrinsics.*;
|
||||||
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter;
|
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter;
|
||||||
import org.jetbrains.kotlin.codegen.stackvalue.BranchedValue;
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
||||||
import org.jetbrains.kotlin.codegen.when.SwitchCodegen;
|
import org.jetbrains.kotlin.codegen.when.SwitchCodegen;
|
||||||
@@ -3059,40 +3058,12 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
v.and(Type.INT_TYPE);
|
v.and(Type.INT_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StackValue generateBooleanAnd(final JetBinaryExpression expression) {
|
private StackValue generateBooleanAnd(JetBinaryExpression expression) {
|
||||||
return StackValue.operation(Type.BOOLEAN_TYPE, new Function1<InstructionAdapter, Unit>() {
|
return StackValue.and(genLazy(expression.getLeft(), Type.BOOLEAN_TYPE), genLazy(expression.getRight(), Type.BOOLEAN_TYPE));
|
||||||
@Override
|
|
||||||
public Unit invoke(InstructionAdapter v) {
|
|
||||||
gen(expression.getLeft(), Type.BOOLEAN_TYPE);
|
|
||||||
Label ifFalse = new Label();
|
|
||||||
v.ifeq(ifFalse);
|
|
||||||
gen(expression.getRight(), Type.BOOLEAN_TYPE);
|
|
||||||
Label end = new Label();
|
|
||||||
v.goTo(end);
|
|
||||||
v.mark(ifFalse);
|
|
||||||
v.iconst(0);
|
|
||||||
v.mark(end);
|
|
||||||
return Unit.INSTANCE$;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StackValue generateBooleanOr(final JetBinaryExpression expression) {
|
private StackValue generateBooleanOr(JetBinaryExpression expression) {
|
||||||
return StackValue.operation(Type.BOOLEAN_TYPE, new Function1<InstructionAdapter, Unit>() {
|
return StackValue.or(genLazy(expression.getLeft(), Type.BOOLEAN_TYPE), genLazy(expression.getRight(), Type.BOOLEAN_TYPE));
|
||||||
@Override
|
|
||||||
public Unit invoke(InstructionAdapter v) {
|
|
||||||
gen(expression.getLeft(), Type.BOOLEAN_TYPE);
|
|
||||||
Label ifTrue = new Label();
|
|
||||||
v.ifne(ifTrue);
|
|
||||||
gen(expression.getRight(), Type.BOOLEAN_TYPE);
|
|
||||||
Label end = new Label();
|
|
||||||
v.goTo(end);
|
|
||||||
v.mark(ifTrue);
|
|
||||||
v.iconst(1);
|
|
||||||
v.mark(end);
|
|
||||||
return Unit.INSTANCE$;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StackValue generateEquals(JetExpression left, JetExpression right, IElementType opToken) {
|
private StackValue generateEquals(JetExpression left, JetExpression right, IElementType opToken) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.jetbrains.annotations.Contract;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.builtins.PrimitiveType;
|
import org.jetbrains.kotlin.builtins.PrimitiveType;
|
||||||
import org.jetbrains.kotlin.codegen.stackvalue.BranchedValue;
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
@@ -171,6 +170,14 @@ public abstract class StackValue {
|
|||||||
return BranchedValue.Companion.createInvertValue(stackValue);
|
return BranchedValue.Companion.createInvertValue(stackValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static StackValue or(@NotNull StackValue left, @NotNull StackValue right) {
|
||||||
|
return new Or(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StackValue and(@NotNull StackValue left, @NotNull StackValue right) {
|
||||||
|
return new And(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static StackValue arrayElement(@NotNull Type type, StackValue array, StackValue index) {
|
public static StackValue arrayElement(@NotNull Type type, StackValue array, StackValue index) {
|
||||||
return new ArrayElement(type, array, index);
|
return new ArrayElement(type, array, index);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun main() {
|
||||||
|
val a = false
|
||||||
|
val b = false
|
||||||
|
val c = false
|
||||||
|
if (a && b && c) {
|
||||||
|
"then"
|
||||||
|
} else {
|
||||||
|
"else"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 ICONST_0
|
||||||
|
// 0 ICONST_1
|
||||||
|
// 3 IFEQ
|
||||||
|
// 0 IFNE
|
||||||
|
// 3 IF
|
||||||
|
// 1 GOTO
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun main() {
|
||||||
|
val a = false
|
||||||
|
val b = false
|
||||||
|
val c = false
|
||||||
|
if (a || b || c) {
|
||||||
|
"then"
|
||||||
|
} else {
|
||||||
|
"else"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 ICONST_0
|
||||||
|
// 0 ICONST_1
|
||||||
|
// 1 IFEQ
|
||||||
|
// 2 IFNE
|
||||||
|
// 3 IF
|
||||||
|
// 1 GOTO
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun main() {
|
||||||
|
val a = false
|
||||||
|
val b = false
|
||||||
|
val c = false
|
||||||
|
if (!(a && b && c)) {
|
||||||
|
"then"
|
||||||
|
} else {
|
||||||
|
"else"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 ICONST_0
|
||||||
|
// 0 ICONST_1
|
||||||
|
// 2 IFEQ
|
||||||
|
// 1 IFNE
|
||||||
|
// 3 IF
|
||||||
|
// 1 GOTO
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun main() {
|
||||||
|
val a = false
|
||||||
|
val b = false
|
||||||
|
val c = false
|
||||||
|
if (!(a || b || c)) {
|
||||||
|
"then"
|
||||||
|
} else {
|
||||||
|
"else"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 ICONST_0
|
||||||
|
// 0 ICONST_1
|
||||||
|
// 0 IFEQ
|
||||||
|
// 3 IFNE
|
||||||
|
// 3 IF
|
||||||
|
// 1 GOTO
|
||||||
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
|
|||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@InnerTestClasses({
|
@InnerTestClasses({
|
||||||
BytecodeTextTestGenerated.BoxingOptimization.class,
|
BytecodeTextTestGenerated.BoxingOptimization.class,
|
||||||
|
BytecodeTextTestGenerated.Conditions.class,
|
||||||
BytecodeTextTestGenerated.Constants.class,
|
BytecodeTextTestGenerated.Constants.class,
|
||||||
BytecodeTextTestGenerated.DeadCodeElimination.class,
|
BytecodeTextTestGenerated.DeadCodeElimination.class,
|
||||||
BytecodeTextTestGenerated.DirectInvoke.class,
|
BytecodeTextTestGenerated.DirectInvoke.class,
|
||||||
@@ -296,6 +297,39 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/bytecodeText/conditions")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class Conditions extends AbstractBytecodeTextTest {
|
||||||
|
public void testAllFilesPresentInConditions() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/conditions"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("conjuction.kt")
|
||||||
|
public void testConjuction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/conditions/conjuction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("disjunction.kt")
|
||||||
|
public void testDisjunction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/conditions/disjunction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("negatedConjuction.kt")
|
||||||
|
public void testNegatedConjuction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/conditions/negatedConjuction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("negatedDisjunction.kt")
|
||||||
|
public void testNegatedDisjunction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/conditions/negatedDisjunction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/constants")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/constants")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user