Skip to content

Commit

Permalink
Merge branch 'master' of github.com:neich/jmockit1
Browse files Browse the repository at this point in the history
  • Loading branch information
neich committed Sep 29, 2016
2 parents 3e41b5e + e265e0b commit d7b1721
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 73 deletions.
11 changes: 5 additions & 6 deletions main/src/mockit/Injectable.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* Such instances can be said to be proper <em>mock objects</em>, in contrast to the mocked instances of a regular
* {@code @Mocked} type.
* <p/>
* When the type of the injectable is {@code String}, a primitive wrapper, or an enum, it is <em>not</em> mocked.
* <p/>
* For the duration of each test where the mock field/parameter is in scope, <em>only one</em> injectable instance is
* mocked; other instances of the same mocked type are not affected.
* For an injectable mocked <em>class</em>, <em>static methods</em> and <em>constructors</em> are <em>not</em> mocked;
Expand All @@ -30,13 +32,10 @@
public @interface Injectable
{
/**
* Specifies a literal value when the type of the injectable mock field/parameter is {@code String}, a primitive
* type, or an enum type.
* For a primitive type, the value provided must be convertible to it.
* Specifies a literal value when the type of the injectable mock field/parameter is {@code String}, a primitive or
* wrapper type, or an enum type.
* For a primitive/wrapper type, the value provided must be convertible to it.
* For an enum type, the given textual value must equal the name of one of the possible enum values.
* <p/>
* When a value is provided for an injectable whose type is {@code String} or an enum type, said type is <em>not</em>
* mocked; otherwise, it is.
*/
String value() default "";
}
15 changes: 9 additions & 6 deletions main/src/mockit/Tested.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
* targeted.
* For each such <em>target</em> field, the value of a still unused injectable of the <em>same</em> type is assigned, if
* any is available.
* Multiple target fields of the same type can be injected from separate injectables, provided each target field has the
* same name as an available injectable of that type.
* When a tested object has multiple target fields of the <em>same</em> type, not just the type but also the
* <em>name</em> of each field will be used when looking for available injectables.
* Finally, if there is no matching and available injectable value for a given target field, it is left unassigned,
* unless the target field is for a <em>required</em> dependency; note that all fields marked with a DI annotation
* (such as {@code @Inject}, {@code Autowired}, etc.) indicate required dependencies by default
Expand All @@ -63,9 +63,12 @@
public @interface Tested
{
/**
* Indicates that each and every field of the tested object should be assigned a value, which can be an
* {@linkplain Injectable @Injectable}, another {@code @Tested} field of a type assignable to the field type, or a
* real (unmocked) instance of the field type.
* Indicates that each field of the tested object that is eligible for injection should be assigned a value, which
* can be an {@linkplain Injectable @Injectable}, another {@code @Tested} field of a type assignable to the field
* type, or a real (unmocked) instance of the field type.
* Non-eligible fields are those that have already being assigned from a constructor, or that have a primitive,
* array, annotation, or JRE type (with the exception of the types described below, which are given special
* treatment).
* <p/>
* For each field of a reference type that would otherwise remain {@code null}, an attempt is made to automatically
* create and recursively initialize a suitable real instance.
Expand All @@ -77,7 +80,7 @@
* In this case, the same rules used for injected fields apply to the parameters of the constructor that gets chosen
* for automatic instantiation.
* <p/>
* Currently, the following standard types (mostly Java EE interfaces) have special support:
* Currently, the following standard types (some of which are Java EE interfaces) have special support:
* <ul>
* <li>
* {@link java.util.logging.Logger}: a logger is automatically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,24 @@ public String getCallerClassName()
StackTrace st = new StackTrace(invocationCause);

int steIndex = 3;
String firstCaller = st.getElement(steIndex).getClassName();
StackTraceElement ste = st.getElement(steIndex);

steIndex += "mockit.internal.expectations.mocking.MockedBridge".equals(firstCaller) ? 2 : 1;
String secondCaller = st.getElement(steIndex).getClassName();

if (secondCaller.contains(".reflect.")) { // called through Reflection
steIndex += 3;
if (ste.getFileName() != null && ste.getLineNumber() == -1 && ste.getMethodName().charAt(0) != '<') {
StackTraceElement steNext = st.getElement(steIndex + 1);

while (true) {
String nextCaller = st.getElement(steIndex).getClassName();
if (steNext.getMethodName().equals(ste.getMethodName())) { // bridge method
ste = steNext;
steIndex++;
}
}

if ("mockit.Deencapsulation".equals(nextCaller)) {
continue;
}
String firstCaller = ste.getClassName();

if (!nextCaller.contains(".reflect.") && !nextCaller.startsWith("mockit.internal.")) {
return nextCaller;
}
}
steIndex += "mockit.internal.expectations.mocking.MockedBridge".equals(firstCaller) ? 2 : 1;
String secondCaller = st.getElement(steIndex).getClassName();

if (secondCaller.contains(".reflect.")) { // called through Reflection
return getNextCallerAfterReflectionCalls(st, steIndex);
}

if (!secondCaller.equals(firstCaller)) {
Expand All @@ -101,6 +99,25 @@ public String getCallerClassName()
return thirdCaller;
}

@Nonnull
private static String getNextCallerAfterReflectionCalls(@Nonnull StackTrace st, int steIndex)
{
steIndex += 3;

while (true) {
String nextCaller = st.getElement(steIndex).getClassName();
steIndex++;

if ("mockit.Deencapsulation".equals(nextCaller)) {
continue;
}

if (!nextCaller.contains(".reflect.") && !nextCaller.startsWith("mockit.internal.")) {
return nextCaller;
}
}
}

// Simple getters //////////////////////////////////////////////////////////////////////////////////////////////////

@Nonnull public String getClassDesc() { return arguments.classDesc; }
Expand Down
5 changes: 3 additions & 2 deletions main/src/mockit/internal/expectations/mocking/MockedType.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import mockit.internal.injection.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
import static mockit.internal.util.AutoBoxing.isWrapperOfPrimitiveType;

@SuppressWarnings("EqualsAndHashcode")
public final class MockedType implements InjectionPointProvider
Expand Down Expand Up @@ -230,8 +231,8 @@ private boolean isMockableType(@Nonnull Class<?> classType)
return false;
}

if (injectable && providedValue != null) {
if (classType == String.class || classType.isEnum()) {
if (injectable) {
if (classType == String.class || isWrapperOfPrimitiveType(classType) || classType.isEnum()) {
return false;
}
}
Expand Down
15 changes: 14 additions & 1 deletion main/src/mockit/internal/injection/InjectionPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,9 @@ static String getQualifiedName(@Nonnull Annotation[] annotationsOnInjectionPoint
if ("javax.annotation.Resource javax.ejb.EJB".contains(annotationName)) {
String name = readAnnotationAttribute(annotation, "name");

if (name == null || name.isEmpty()) {
if (name.isEmpty()) {
name = readAnnotationAttribute(annotation, "lookup");
name = getNameFromJNDILookup(name);
}

return name;
Expand All @@ -261,4 +262,16 @@ else if ("javax.inject.Named".equals(annotationName) || annotationName.endsWith(

return null;
}

@Nonnull
static String getNameFromJNDILookup(@Nonnull String jndiLookup)
{
int p = jndiLookup.lastIndexOf('/');

if (p >= 0) {
jndiLookup = jndiLookup.substring(p + 1);
}

return jndiLookup;
}
}
2 changes: 1 addition & 1 deletion main/src/mockit/internal/injection/TestDataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private void createDataSource(@Nonnull DataSourceDefinitions dsDefs)

private void createDataSource(@Nonnull DataSourceDefinition dsDef)
{
String configuredDataSourceName = dsDef.name();
String configuredDataSourceName = InjectionPoint.getNameFromJNDILookup(dsDef.name());

if (configuredDataSourceName.equals(dsName)) {
instantiateConfiguredDataSourceClass(dsDef);
Expand Down
8 changes: 5 additions & 3 deletions main/src/mockit/internal/startup/AgentLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public final class AgentLoader
throw new IllegalStateException("JMockit requires a Java 6+ VM");
}

jarFilePath = new PathToAgentJar().getPathToJarFile();
jarFilePath = PathToAgentJar.getPathToJarFile();
}

public AgentLoader(@Nonnull String pid)
Expand Down Expand Up @@ -100,10 +100,12 @@ private static Class<? extends VirtualMachine> findVirtualMachineClassAccordingT
if (osName.startsWith("Linux") || osName.startsWith("LINUX")) {
return LinuxVirtualMachine.class;
}
else if (osName.contains("FreeBSD") || osName.startsWith("Mac OS X")) {

if (osName.contains("FreeBSD") || osName.startsWith("Mac OS X")) {
return BsdVirtualMachine.class;
}
else if (osName.startsWith("Solaris") || osName.contains("SunOS")) {

if (osName.startsWith("Solaris") || osName.contains("SunOS")) {
return SolarisVirtualMachine.class;
}

Expand Down
55 changes: 38 additions & 17 deletions main/src/mockit/internal/startup/PathToAgentJar.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package mockit.internal.startup;

import java.io.*;
import java.security.*;
import java.util.jar.*;
import java.util.regex.*;
import javax.annotation.*;
Expand All @@ -16,21 +17,15 @@ final class PathToAgentJar
private static final Pattern JAR_REGEX = Pattern.compile(".*jmockit[-.\\d]*.jar");

@Nonnull
String getPathToJarFile()
static String getPathToJarFile()
{
String jarFilePath = findPathToJarFileFromClasspath();

if (jarFilePath == null) {
jarFilePath = createBootstrappingJarFileInTempDir();
jarFilePath = findOrCreateBootstrappingJarFileInTempDir();
}

if (jarFilePath != null) {
return jarFilePath;
}

throw new IllegalStateException(
"No jar file with name ending in \"jmockit.jar\" or \"jmockit-nnn.jar\" (where \"nnn\" is a version number) " +
"found in the classpath");
return jarFilePath;
}

@Nullable
Expand All @@ -47,8 +42,39 @@ private static String findPathToJarFileFromClasspath()
return null;
}

@Nullable
private String createBootstrappingJarFileInTempDir()
@Nonnull
private static String findOrCreateBootstrappingJarFileInTempDir()
{
String tempDir = System.getProperty("java.io.tmpdir");
String currentVersion = currentVersion();
File bootstrapJar = new File(tempDir, "jmockitAgent-" + currentVersion + ".jar");

if (bootstrapJar.exists()) {
return bootstrapJar.getPath();
}

createBootstrapJarInTempDir(bootstrapJar);

return bootstrapJar.getPath();
}

@Nonnull
private static String currentVersion()
{
Class<?> thisClass = PathToAgentJar.class;
String currentVersion = thisClass.getPackage().getImplementationVersion();

if (currentVersion == null) { // only happens when the class is loaded from the main/target/classes dir
ProtectionDomain pd = thisClass.getProtectionDomain();
String versionFile = pd.getCodeSource().getLocation().getPath() + "../../../version.txt";
try { currentVersion = new RandomAccessFile(versionFile, "r").readLine(); }
catch (IOException e) { throw new RuntimeException(e); }
}

return currentVersion;
}

private static void createBootstrapJarInTempDir(@Nonnull File bootstrapJar)
{
Manifest manifest = new Manifest();
Attributes attrs = manifest.getMainAttributes();
Expand All @@ -60,16 +86,11 @@ private String createBootstrappingJarFileInTempDir()
byte[] classFile = ClassFile.readFromFile(InstrumentationHolder.class).b;

try {
File tempJar = File.createTempFile("jmockit", ".jar");
tempJar.deleteOnExit();

JarOutputStream output = new JarOutputStream(new FileOutputStream(tempJar), manifest);
JarOutputStream output = new JarOutputStream(new FileOutputStream(bootstrapJar), manifest);
JarEntry classEntry = new JarEntry(InstrumentationHolder.class.getName().replace('.', '/') + ".class");
output.putNextEntry(classEntry);
output.write(classFile);
output.close();

return tempJar.getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
Expand Down
18 changes: 7 additions & 11 deletions main/src/mockit/internal/util/MethodReflection.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,17 @@ public static <T> T invokePublicIfAvailable(
return result;
}

@Nullable
public static <T> T readAnnotationAttribute(@Nonnull Object annotationInstance, @Nonnull String attributeName)
@Nonnull
public static String readAnnotationAttribute(@Nonnull Object annotationInstance, @Nonnull String attributeName)
{
T result = null;

try {
Method publicMethod = annotationInstance.getClass().getMethod(attributeName, NO_PARAMETERS);
//noinspection unchecked
result = (T) publicMethod.invoke(annotationInstance);
String result = (String) publicMethod.invoke(annotationInstance);
return result;
}
catch (NoSuchMethodException ignore) {}
catch (IllegalAccessException ignore) {}
catch (InvocationTargetException ignore) {}

return result;
catch (NoSuchMethodException e) { throw new RuntimeException(e); }
catch (IllegalAccessException e) { throw new RuntimeException(e); }
catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); }
}

@Nullable
Expand Down
14 changes: 14 additions & 0 deletions main/test/mockit/ExpectationsUsingMockedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,18 @@ public void getBeanInfoFromMockedInterface(@Mocked BusinessInterface mock) throw

assertNotNull(info);
}

static class GenericBase<B extends Runnable> { public B base() { return null; } }
public static final class GenericSubclass<S extends Runnable> extends GenericBase<S> { /* bridge method here */ }

@Test
public void recordExpectationOnBaseMethodHavingASyntheticBridgeMethodInSubclass(@Mocked final GenericSubclass mock)
{
new Expectations() {{
mock.base();
result = null;
}};

assertNull(mock.base());
}
}
10 changes: 0 additions & 10 deletions main/test/mockit/InstanceSpecificMockingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,6 @@ public void allowInjectableMockOfAnnotationType(@Injectable final RunWith runWit
assertSame(BlockJUnit4ClassRunner.class, runWith.value());
}

enum EnumWithMethod { Value1; int someMethod() { return 123; } }

@Test
public void allowInjectableMockOfEnumType(@Injectable final EnumWithMethod enumWithMethod)
{
new Expectations() {{ enumWithMethod.someMethod(); result = 45; }};

assertEquals(45, enumWithMethod.someMethod());
}

// Mocking java.nio.ByteBuffer /////////////////////////////////////////////////////////////////////////////////////

@Test
Expand Down
2 changes: 1 addition & 1 deletion main/test/mockit/TestedClassWithConstructorDI0Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public TestedClassWithConstructorHavingDoubleSizeParameterFollowedByRegularParam
@Injectable byte b = 56;
@Injectable byte b2 = 57;
@Injectable char c = 'X';
@Injectable String s = "test"; // String is mocked
@Injectable String s = "test";
@Injectable double d1 = 1.0;
@Injectable double d2 = 2.0;

Expand Down

0 comments on commit d7b1721

Please sign in to comment.