/**
* Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
*/
package org.evosuite.testcase.statements;
import com.examples.with.different.packagename.fm.IssueWithNumber;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.evosuite.Properties;
import org.evosuite.classpath.ClassPathHandler;
import org.evosuite.instrumentation.BytecodeInstrumentation;
import org.evosuite.instrumentation.InstrumentingClassLoader;
import org.evosuite.instrumentation.NonInstrumentingClassLoader;
import org.evosuite.runtime.MockitoExtension;
import org.evosuite.runtime.RuntimeSettings;
import org.evosuite.runtime.instrumentation.EvoClassLoader;
import org.evosuite.runtime.instrumentation.RuntimeInstrumentation;
import org.evosuite.testcase.DefaultTestCase;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.TestChromosome;
import org.evosuite.testcase.TestFactory;
import org.evosuite.testcase.execution.Scope;
import org.evosuite.testcase.statements.numeric.BooleanPrimitiveStatement;
import org.evosuite.testcase.statements.numeric.IntPrimitiveStatement;
import org.evosuite.testcase.variable.ArrayIndex;
import org.evosuite.testcase.variable.ArrayReference;
import org.evosuite.testcase.variable.VariableReference;
import org.evosuite.testcase.variable.VariableReferenceImpl;
import org.evosuite.utils.generic.GenericMethod;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test;
import sun.misc.ClassLoaderUtil;
import java.io.File;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Created by Andrea Arcuri on 06/08/15.
*/
public class FunctionalMockStatementTest {
private static final int DEFAULT_LIMIT = Properties.FUNCTIONAL_MOCKING_INPUT_LIMIT;
@After
public void tearDown(){
Properties.FUNCTIONAL_MOCKING_INPUT_LIMIT = DEFAULT_LIMIT;
}
public interface Foo{
boolean getBoolean();
int getInt();
double getDouble();
String getString();
long getLong();
Object getObject();
String[] getStringArray(int[] input);
}
public static int base(Foo foo){
return foo.getInt();
}
public static void all_once(Foo foo){
foo.getBoolean();
foo.getInt();
foo.getDouble();
foo.getString();
foo.getLong();
foo.getObject();
foo.getStringArray(null);
}
public static void all_twice(Foo foo){
all_once(foo);
all_once(foo);
}
public static String getFirstInArray(Foo foo){
int[] anArray = new int[]{123};
String[] res = foo.getStringArray(anArray);
if(res==null){
return null;
}
return res[0];
}
public static void limit(Foo foo, int x){
for(int i=0; i<x ; i++){
foo.getBoolean();
}
}
private Scope execute(TestCase tc) throws Exception{
Scope scope = new Scope();
for(Statement st : tc){
st.execute(scope,System.out);
}
return scope;
}
static class PackageLevel{
PackageLevel(){}
}
public static class AClassWithPLMethod{
String foo(){
return "Value returned by package-level access method";
}
}
public static class OverrideToString{
@Override
public String toString(){
return "foo";
}
}
public static abstract class OverrideToStringAbstract implements java.io.Serializable {
@Override
public String toString(){
return "foo";
}
public abstract double foo();
public int bar(){return 1;}
private static final long serialVersionUID = -8742448824652078965L;
}
//----------------------------------------------------------------------------------
@Test
public void testAClassWithPLMethod(){
//FIXME once we support it
assertFalse(FunctionalMockStatement.canBeFunctionalMocked(AClassWithPLMethod.class));
}
@Test
public void testConfirmToString(){
String res = new OverrideToString().toString();
String diff = res + " a different string";
OverrideToString obj = mock(OverrideToString.class);
when(obj.toString()).thenReturn(diff);
assertEquals(diff, obj.toString());
}
@Test
public void testConfirmToStringAbstract(){
String diff = " a different string";
OverrideToStringAbstract obj = mock(OverrideToStringAbstract.class);
when(obj.toString()).thenReturn(diff);
assertEquals(diff, obj.toString());
}
@Test
public void testConfirmNumber(){
String foo = "foo";
Number number = mock(Number.class);
when(number.toString()).thenReturn(foo);
assertEquals(foo, number.toString());
}
@Test
public void testConfirmNumberExternalNoMockJVMNonDeterminism() throws Exception{
RuntimeSettings.mockJVMNonDeterminism = false;
testConfirmNumberExternal();
}
@Test
public void testConfirmNumberExternalWithMockJVMNonDeterminism() throws Exception{
RuntimeSettings.mockJVMNonDeterminism = true;
testConfirmNumberExternal();
}
private void testConfirmNumberExternal() throws Exception{
assertEquals(IssueWithNumber.RESULT, IssueWithNumber.getResult());
RuntimeInstrumentation.setAvoidInstrumentingShadedClasses(true);
ClassPathHandler.getInstance().changeTargetCPtoTheSameAsEvoSuite();
EvoClassLoader loader = new EvoClassLoader();
loader.skipInstrumentation(IssueWithNumber.class.getName());
org.evosuite.runtime.Runtime.getInstance().resetRuntime();
Class<?> klass = loader.loadClass(IssueWithNumber.class.getName());
Method m = klass.getDeclaredMethod("getResult");
String res = (String) m.invoke(null);
assertEquals(IssueWithNumber.RESULT, res);
}
@Test
public void testConfirmPackageLevel() throws Exception{
Method m = AClassWithPLMethod.class.getDeclaredMethod("foo");
assertFalse(Modifier.isPrivate(m.getModifiers()));
assertFalse(Modifier.isPublic(m.getModifiers()));
assertFalse(Modifier.isProtected(m.getModifiers()));
}
@Test
public void testConfirmMockitoBehaviorOnPackageLevelAccess() throws Exception {
//direct calls
AClassWithPLMethod original = new AClassWithPLMethod();
assertNotNull(original.foo());
AClassWithPLMethod mocked = mock(AClassWithPLMethod.class);
assertNull(mocked.foo());
//reflection
Method m = AClassWithPLMethod.class.getDeclaredMethod("foo");
m.setAccessible(true);
assertNotNull(m.invoke(original));
assertNull(m.invoke(mocked));
}
@Test
public void testConfirmCast(){
//note: TypeUtils can give different results because it takes autoboxing into account
assertTrue(TypeUtils.isAssignable(Integer.class, Integer.TYPE));
assertTrue(TypeUtils.isAssignable(Integer.TYPE, Integer.class));
assertFalse(Integer.TYPE.isAssignableFrom(Integer.class));
assertFalse(Integer.class.isAssignableFrom(Integer.TYPE));
assertFalse(Integer.TYPE.isAssignableFrom(Character.TYPE));
assertFalse(TypeUtils.isAssignable(Integer.TYPE, Character.TYPE));
assertFalse(Character.TYPE.isAssignableFrom(Integer.TYPE));
assertTrue(TypeUtils.isAssignable(Character.TYPE, Integer.TYPE)); //DIFFERENT
assertFalse(Character.class.isAssignableFrom(Integer.TYPE));
assertTrue(TypeUtils.isAssignable(Character.class, Integer.TYPE)); //DIFFERENT
assertFalse(Character.class.isAssignableFrom(Integer.class));
assertFalse(TypeUtils.isAssignable(Character.class, Integer.class));
assertTrue(Integer.TYPE.isPrimitive());
assertFalse(Integer.class.isPrimitive());
char c = 'c'; //99
int i = c;
assertEquals(99, i);
Object aInt = i;
Object aInteger = Integer.valueOf(7);
Assert.assertTrue(aInt.getClass().equals(Integer.class));
Assert.assertTrue(aInt.getClass().equals(aInteger.getClass()));
Object aChar = c;
Assert.assertTrue(aChar.getClass().equals(Character.class));
//just recall the two diverge
assertTrue(TypeUtils.isAssignable(aChar.getClass(), Integer.TYPE));
assertFalse(Integer.TYPE.isAssignableFrom(aChar.getClass()));
Object casted = null;
try {
casted = Integer.TYPE.cast(aChar);
fail();
} catch (Exception e){
//expected: cannot do direct cast from "Character" to "int"
}
try {
casted = Integer.TYPE.cast(((Character) aChar).charValue());
fail();
} catch (Exception e){
//expected: "cast" takes an Object as input, so it does autoboxing :(
}
casted = (int) ((Character) aChar).charValue();
assertTrue(casted.getClass().equals(Integer.class));
}
@Test
public void testAvoidMockingEnvironment(){
final boolean defaultValue = RuntimeSettings.useVFS;
RuntimeSettings.useVFS = true;
try {
Assert.assertFalse(FunctionalMockStatement.canBeFunctionalMocked(File.class));
} catch(Throwable t){
RuntimeSettings.useVFS = defaultValue;
}
}
@Test
public void testPackageLevel_local() throws Exception{
TestCase tc = new DefaultTestCase();
VariableReference ref = new VariableReferenceImpl(tc, PackageLevel.class);
try {
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, PackageLevel.class);
fail();
} catch (java.lang.IllegalArgumentException e){
//expected
}
//tc.addStatement(mockStmt);
//execute(tc);
}
@Test
public void testPackageLevel_differentPackage() throws Exception{
TestCase tc = new DefaultTestCase();
Class<?> example = Class.forName("com.examples.with.different.packagename.fm.ExamplePackageLevel");
VariableReference ref = new VariableReferenceImpl(tc, example);
try {
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, example);
fail();
} catch (java.lang.IllegalArgumentException e){
//expected
}
//tc.addStatement(mockStmt);
//execute(tc);
}
@Test
public void testPackageLevel_differentPackage_instrumentation_package() throws Exception{
TestCase tc = new DefaultTestCase();
ClassPathHandler.getInstance().changeTargetCPtoTheSameAsEvoSuite();
InstrumentingClassLoader loader = new InstrumentingClassLoader();
Class<?> example = loader.loadClass("com.examples.with.different.packagename.fm.ExamplePackageLevel");
VariableReference ref = new VariableReferenceImpl(tc, example);
try {
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, example);
fail();
} catch (java.lang.IllegalArgumentException e){
//expected
}
//tc.addStatement(mockStmt);
//execute(tc);
}
@Test
public void testPackageLevel_differentPackage_nonInstrumentation_package() throws Exception{
TestCase tc = new DefaultTestCase();
ClassPathHandler.getInstance().changeTargetCPtoTheSameAsEvoSuite();
NonInstrumentingClassLoader loader = new NonInstrumentingClassLoader();
Class<?> example = loader.loadClass("com.examples.with.different.packagename.fm.ExamplePackageLevel");
VariableReference ref = new VariableReferenceImpl(tc, example);
try {
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, example);
fail();
} catch (java.lang.IllegalArgumentException e){
//expected
}
//tc.addStatement(mockStmt);
//execute(tc);
}
@Test
public void testPackageLevel_differentPackage_instrumentation_public() throws Exception{
TestCase tc = new DefaultTestCase();
ClassPathHandler.getInstance().changeTargetCPtoTheSameAsEvoSuite();
InstrumentingClassLoader loader = new InstrumentingClassLoader();
Class<?> example = loader.loadClass("com.examples.with.different.packagename.fm.ExamplePublicLevel");
VariableReference ref = new VariableReferenceImpl(tc, example);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, example);
tc.addStatement(mockStmt);
execute(tc);
}
@Test
public void testLimit() throws Exception{
TestCase tc = new DefaultTestCase();
final int LIMIT_5 = 5;
Properties.FUNCTIONAL_MOCKING_INPUT_LIMIT = LIMIT_5;
final int LOOP_0 = 0, LOOP_3 = 3 , LOOP_5 = 5, LOOP_7 = 7;
IntPrimitiveStatement x = new IntPrimitiveStatement(tc, LOOP_3);
VariableReference loop = tc.addStatement(x);
VariableReference boolRef = tc.addStatement(new BooleanPrimitiveStatement(tc,true));
VariableReference ref = new VariableReferenceImpl(tc, Foo.class);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, Foo.class);
VariableReference mock = tc.addStatement(mockStmt);
tc.addStatement(new MethodStatement(tc,
new GenericMethod(this.getClass().getDeclaredMethod("limit", Foo.class, int.class), FunctionalMockStatementTest.class),
null, Arrays.asList(mock,loop)));
//execute first time with default mock
execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
List<Type> types = mockStmt.updateMockedMethods();
Assert.assertEquals(LOOP_3, types.size());
for(Type t : types){
Assert.assertEquals(boolean.class , t);
}
//add the 3 missing values
mockStmt.addMissingInputs(Arrays.asList(boolRef, boolRef, boolRef));
//before re-executing, change loops to the limit
x.setValue(LOOP_5);
execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
types = mockStmt.updateMockedMethods();
Assert.assertEquals(LOOP_5 - LOOP_3, types.size());
for(Type t : types){
Assert.assertEquals(boolean.class , t);
}
//add the 2 missing values
mockStmt.addMissingInputs(Arrays.asList(boolRef, boolRef));
Assert.assertEquals(LOOP_5, mockStmt.getNumParameters());
//before re-executing 3rd time, change loops above the limit
x.setValue(LOOP_7);
execute(tc);
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs()); //no update should be required
types = mockStmt.updateMockedMethods();
Assert.assertEquals(0, types.size());
Assert.assertEquals(LOOP_5, mockStmt.getNumParameters());
//decrease, but to the limit, so still no required change
x.setValue(LOOP_5);
execute(tc);
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs()); //no update should be required
types = mockStmt.updateMockedMethods();
Assert.assertEquals(0, types.size());
Assert.assertEquals(LOOP_5, mockStmt.getNumParameters());
//further decrease, but now we need to remove parameters
x.setValue(LOOP_3);
execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs()); //do update
types = mockStmt.updateMockedMethods();
Assert.assertEquals(0, types.size()); // but no new types to add
Assert.assertEquals(LOOP_3, mockStmt.getNumParameters());
//remove all
x.setValue(LOOP_0);
execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs()); //do update
types = mockStmt.updateMockedMethods();
Assert.assertEquals(0, types.size()); // but no new types to add
Assert.assertEquals(LOOP_0, mockStmt.getNumParameters());
}
@Test
public void testAll_once() throws Exception {
TestCase tc = new DefaultTestCase();
VariableReference ref = new VariableReferenceImpl(tc, Foo.class);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, Foo.class);
VariableReference mock = tc.addStatement(mockStmt);
VariableReference result = tc.addStatement(new MethodStatement(tc,
new GenericMethod(this.getClass().getDeclaredMethod("all_once", Foo.class), FunctionalMockStatementTest.class),
null, Arrays.asList(mock)));
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs());
Assert.assertEquals(0, mockStmt.getNumParameters());
//execute first time with default mock
Scope scope = execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
List<Type> types = mockStmt.updateMockedMethods();
Assert.assertEquals(7,types.size());
}
@Test
public void testAll_twice() throws Exception {
TestCase tc = new DefaultTestCase();
VariableReference ref = new VariableReferenceImpl(tc, Foo.class);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, Foo.class);
VariableReference mock = tc.addStatement(mockStmt);
VariableReference result = tc.addStatement(new MethodStatement(tc,
new GenericMethod(this.getClass().getDeclaredMethod("all_twice", Foo.class), FunctionalMockStatementTest.class),
null, Arrays.asList(mock)));
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs());
Assert.assertEquals(0, mockStmt.getNumParameters());
//execute first time with default mock
Scope scope = execute(tc);
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
List<Type> types = mockStmt.updateMockedMethods();
Assert.assertEquals(14,types.size());
}
@Test
public void testArray() throws Exception{
TestCase tc = new DefaultTestCase();
/*
String s = "...";
String[] array = new String[1];
array[0] = s;
Foo foo = mock(Foo.class);
getFirstInArray(foo);
*/
final String MOCKED_VALUE = "Hello 42!!!";
VariableReference aString = tc.addStatement(new StringPrimitiveStatement(tc, MOCKED_VALUE));
ArrayReference mockedArray = (ArrayReference) tc.addStatement(new ArrayStatement(tc,String[].class,1));
ArrayIndex arrayIndex = new ArrayIndex(tc, mockedArray, 0);
AssignmentStatement stmt = new AssignmentStatement(tc, arrayIndex, aString);
tc.addStatement(stmt);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, Foo.class, Foo.class);
VariableReference mock = tc.addStatement(mockStmt);
VariableReference result = tc.addStatement(new MethodStatement(tc,
new GenericMethod(this.getClass().getDeclaredMethod("getFirstInArray", Foo.class), FunctionalMockStatementTest.class),
null, Arrays.asList(mock)));
//if not executed, should be no way to tell if needs new inputs
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs());
Assert.assertEquals(0, mockStmt.getNumParameters());
//execute first time with default mock
Scope scope = execute(tc);
Object obj = scope.getObject(result);
Assert.assertNull(obj); // default mock value should be null for objects/arrays
//after execution, there should be one variable to provide
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
List<Type> types = mockStmt.updateMockedMethods();
Assert.assertEquals(1,types.size());
Assert.assertEquals(String[].class, types.get(0));
//add int variable to list of mock expected returns
mockStmt.addMissingInputs(Arrays.asList(mockedArray));
Assert.assertEquals(1, mockStmt.getNumParameters());
Assert.assertTrue(mockStmt.getParameterReferences().get(0).same(mockedArray));
//re-execute with initialized mock
scope = execute(tc);
String val = (String) scope.getObject(result);
Assert.assertEquals(MOCKED_VALUE, val);
}
@Test
public void testBase() throws Exception {
TestCase tc = new DefaultTestCase();
final int MOCKED_VALUE = 42;
VariableReference mockedInput = tc.addStatement(new IntPrimitiveStatement(tc, MOCKED_VALUE));
VariableReference ref = new VariableReferenceImpl(tc, Foo.class);
FunctionalMockStatement mockStmt = new FunctionalMockStatement(tc, ref, Foo.class);
VariableReference mock = tc.addStatement(mockStmt);
VariableReference result = tc.addStatement(new MethodStatement(tc,
new GenericMethod(this.getClass().getDeclaredMethod("base", Foo.class), FunctionalMockStatementTest.class),
null, Arrays.asList(mock)));
//if not executed, should be no way to tell if needs new inputs
Assert.assertFalse(mockStmt.doesNeedToUpdateInputs());
Assert.assertEquals(0, mockStmt.getNumParameters());
//execute first time with default mock
Scope scope = execute(tc);
Integer val = (Integer) scope.getObject(result);
Assert.assertEquals(0 , val.intValue()); // default mock value should be 0
//after execution, there should be one variable to provide
Assert.assertTrue(mockStmt.doesNeedToUpdateInputs());
List<Type> types = mockStmt.updateMockedMethods();
Assert.assertEquals(1,types.size());
Assert.assertEquals(int.class, types.get(0));
//add int variable to list of mock expected returns
mockStmt.addMissingInputs(Arrays.asList(mockedInput));
Assert.assertEquals(1, mockStmt.getNumParameters());
Assert.assertTrue(mockStmt.getParameterReferences().get(0).same(mockedInput));
//re-execute with initialized mock
scope = new Scope();
for(Statement st : tc){
st.execute(scope,System.out);
}
val = (Integer) scope.getObject(result);
Assert.assertEquals(MOCKED_VALUE, val.intValue());
}
}