Permalink
Browse files

Merge pull request #336 from zoodles/master

New annotation and features improvement
  • Loading branch information...
2 parents 8a59488 + ed0782d commit fb8daf727cee503ae5a95271dd756120f4ec085e @mportuesisf mportuesisf committed Oct 1, 2012
@@ -1277,18 +1277,31 @@ public static Object newInstanceOf(String className) {
public static void setFinalStaticField(Class classWhichContainsField, String fieldName, Object newValue) {
try {
Field field = classWhichContainsField.getDeclaredField(fieldName);
- field.setAccessible(true);
+ setFinalStaticField(field, newValue);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Object setFinalStaticField(Field field, Object newValue) {
+ Object oldValue = null;
+
+ try {
+ field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
- field.set(null, newValue);
+ oldValue = field.get(null);
+ field.set(null, newValue);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
+
+ return oldValue;
}
}
@@ -6,7 +6,9 @@
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -26,6 +28,7 @@
import android.app.Application;
import android.net.Uri__FromAndroid;
+import com.xtremelabs.robolectric.annotation.WithConstantString;
import com.xtremelabs.robolectric.bytecode.ClassHandler;
import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
@@ -283,9 +286,20 @@ protected void delegateLoadingOf(final String className) {
final Statement statement = super.methodBlock(method);
return new Statement() {
@Override public void evaluate() throws Throwable {
- // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
+ HashMap<Field,Object> withConstantAnnos = getWithConstantAnnotations(method.getMethod());
+
+ // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
try {
- statement.evaluate();
+ if (withConstantAnnos.isEmpty()) {
+ statement.evaluate();
+ }
+ else {
+ synchronized(this) {
+ setupConstants(withConstantAnnos);
+ statement.evaluate();
+ setupConstants(withConstantAnnos);
+ }
+ }
} finally {
delegate.internalAfterTest(method.getMethod());
if (classHandler != null) {
@@ -409,7 +423,7 @@ private void setupI18nStrictState(Method method, RobolectricConfig robolectricCo
robolectricConfig.setStrictI18n(strictI18n);
}
-
+
/**
* Default implementation of global switch for i18n-strict mode.
* To enable i18n-strict mode globally, set the system property
@@ -469,6 +483,77 @@ private void lookForLocaleAnnotation( Method method, RobolectricConfig robolectr
robolectricConfig.setLocale( locale );
}
+
+ /**
+ * Find all the class and method annotations and pass them to
+ * addConstantFromAnnotation() for evaluation.
+ *
+ * TODO: Add compound annotations to suport defining more than one int and string at a time
+ * TODO: See http://stackoverflow.com/questions/1554112/multiple-annotations-of-the-same-type-on-one-element
+ *
+ * @param method
+ * @return
+ */
+ private HashMap<Field,Object> getWithConstantAnnotations(Method method) {
+ HashMap<Field,Object> constants = new HashMap<Field,Object>();
+
+ for(Annotation anno:method.getDeclaringClass().getAnnotations()) {
+ addConstantFromAnnotation(constants, anno);
+ }
+
+ for(Annotation anno:method.getAnnotations()) {
+ addConstantFromAnnotation(constants, anno);
+ }
+
+ return constants;
+ }
+
+ /**
+ * If the annotation is a constant redefinition, add it to the provided hash
+ *
+ * @param constants
+ * @param anno
+ */
+ private void addConstantFromAnnotation(HashMap<Field,Object> constants, Annotation anno) {
+ try {
+ String name = anno.annotationType().getName();
+ Object newValue = null;
+
+ if (name.equals( "com.xtremelabs.robolectric.annotation.WithConstantString" )) {
+ newValue = (String) anno.annotationType().getMethod("newValue").invoke(anno);
+ }
+ else if (name.equals( "com.xtremelabs.robolectric.annotation.WithConstantInt" )) {
+ newValue = (Integer) anno.annotationType().getMethod("newValue").invoke(anno);
+ }
+ else {
+ return;
+ }
+
+ @SuppressWarnings("rawtypes")
+ Class classWithField = (Class) anno.annotationType().getMethod("classWithField").invoke(anno);
+ String fieldName = (String) anno.annotationType().getMethod("fieldName").invoke(anno);
+ Field field = classWithField.getDeclaredField(fieldName);
+ constants.put(field, newValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Defines static finals from the provided hash and stores the old values back
+ * into the hash.
+ *
+ * Call it twice with the same hash, and it puts everything back the way it was originally.
+ *
+ * @param constants
+ */
+ private void setupConstants(HashMap<Field,Object> constants) {
+ for(Field field:constants.keySet()) {
+ Object newValue = constants.get(field);
+ Object oldValue = Robolectric.Reflection.setFinalStaticField(field, newValue);
+ constants.put(field,oldValue);
+ }
+ }
private void setupLogging() {
String logging = System.getProperty("robolectric.logging");
@@ -0,0 +1,15 @@
+package com.xtremelabs.robolectric.annotation;
+
+/**
+ * Annotation to run test with setFinalStaticField() defined in a synchronized
+ * block with automatic reversion to the original value.
+ */
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE,java.lang.annotation.ElementType.METHOD})
+public @interface WithConstantInt {
+ @SuppressWarnings("rawtypes")
+ Class classWithField();
+ String fieldName();
+ int newValue();
+}
@@ -0,0 +1,15 @@
+package com.xtremelabs.robolectric.annotation;
+
+/**
+ * Annotation to run test with setFinalStaticField() defined in a synchronized
+ * block with automatic reversion to the original value.
+ */
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE,java.lang.annotation.ElementType.METHOD})
+public @interface WithConstantString {
+ @SuppressWarnings("rawtypes")
+ Class classWithField();
+ String fieldName();
+ String newValue();
+}
@@ -1,13 +1,12 @@
package com.xtremelabs.robolectric.res;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.Map.Entry;
import android.content.ComponentName;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ResolveInfo;
@@ -23,7 +22,8 @@
private Map<ComponentName, ComponentState> componentList = new HashMap<ComponentName,ComponentState>();
private Map<ComponentName, Drawable> drawableList = new HashMap<ComponentName, Drawable>();
private Map<String, Boolean> systemFeatureList = new HashMap<String, Boolean>();
-
+ private Map<IntentFilter, ComponentName > preferredActivities = new HashMap<IntentFilter, ComponentName>();
+
private ContextWrapper contextWrapper;
private RobolectricConfig config;
private ApplicationInfo applicationInfo;
@@ -135,6 +135,50 @@ public CharSequence getApplicationLabel(ApplicationInfo info) {
public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
componentList.put(componentName, new ComponentState(newState, flags));
}
+
+ public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) {
+ preferredActivities.put(filter, activity);
+ }
+
+ @Override
+ public int getPreferredActivities(List<IntentFilter> outFilters, List<ComponentName> outActivities, String packageName) {
+ if( outFilters == null ){ return 0; }
+
+ Set< IntentFilter> filters = preferredActivities.keySet();
+ for( IntentFilter filter: outFilters ){
+ step:
+ for ( IntentFilter testFilter : filters ) {
+ ComponentName name = preferredActivities.get( testFilter );
+ // filter out based on the given packageName;
+ if( packageName != null && !name.getPackageName().equals( packageName ) ){
+ continue step;
+ }
+
+ // Check actions
+ Iterator< String > iterator = filter.actionsIterator();
+ while ( iterator.hasNext() ) {
+ if ( !testFilter.matchAction( iterator.next() ) ) {
+ continue step;
+ }
+ }
+
+ iterator = filter.categoriesIterator();
+ while ( iterator.hasNext() ) {
+ if ( !filter.hasCategory( iterator.next() ) ) {
+ continue step;
+ }
+ }
+
+ if( outActivities == null ){
+ outActivities = new ArrayList<ComponentName>();
+ }
+
+ outActivities.add( name );
+ }
+ }
+
+ return 0;
+ }
/**
* Non-Android accessor. Use to make assertions on values passed to
@@ -16,6 +16,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
import static com.xtremelabs.robolectric.Robolectric.shadowOf;
@@ -61,7 +63,19 @@ public static Bitmap decodeStream(InputStream is) {
public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) {
return create(is.toString().replaceFirst("stream for ", ""), opts);
}
+
+ @Implementation
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
+ return decodeByteArray( data, offset, length, new BitmapFactory.Options() );
+ }
+ @Implementation
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length, BitmapFactory.Options opts) {
+ Checksum checksumEngine = new CRC32();
+ checksumEngine.update(data, 0, data.length);
+ return create("byte array, checksum:" + checksumEngine.getValue() + " offset: " + offset + " length: " + data.length, opts );
+ }
+
static Bitmap create(String name) {
return create(name, new BitmapFactory.Options());
}
@@ -1,23 +1,29 @@
package com.xtremelabs.robolectric.shadows;
-import android.content.IntentFilter;
-import com.xtremelabs.robolectric.internal.Implementation;
-import com.xtremelabs.robolectric.internal.Implements;
+import static com.xtremelabs.robolectric.Robolectric.shadowOf_;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
+
+import android.content.IntentFilter;
+
+import com.xtremelabs.robolectric.internal.Implementation;
+import com.xtremelabs.robolectric.internal.Implements;
/**
* Shadow of {@code IntentFilter} implemented with a {@link java.util.List}
*/
@SuppressWarnings({"UnusedDeclaration"})
@Implements(IntentFilter.class)
public class ShadowIntentFilter {
- List<String> actions = new ArrayList<String>();
+
+ List<String> actions = new ArrayList<String>();
List<String> schemes = new ArrayList<String>();
List<IntentFilter.AuthorityEntry> authoritites = new ArrayList<IntentFilter.AuthorityEntry>();
-
+ List<String> categories = new ArrayList<String>();
+
public void __constructor__(String action) {
actions.add(action);
}
@@ -66,4 +72,58 @@ public void addDataScheme(String scheme) {
public String getDataScheme(int index) {
return schemes.get(index);
}
+
+ @Implementation
+ public void addCategory( String category ) {
+ categories.add( category );
+ }
+
+ @Implementation
+ public boolean hasCategory( String category ) {
+ return categories.contains( category );
+ }
+
+ @Implementation
+ public Iterator<String> categoriesIterator() {
+ return categories.iterator();
+ }
+
+ @Implementation
+ public String getCategory( int index ) {
+ return categories.get( index );
+ }
+
+ @Implementation
+ public boolean matchCategories(Set<String> categories){
+ for( String category: this.categories ){
+ if( !categories.contains( category ) ){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override @Implementation
+ public boolean equals(Object o) {
+ if (o == null) return false;
+ o = shadowOf_(o);
+ if (o == null) return false;
+ if (this == o) return true;
+ if (getClass() != o.getClass()) return false;
+
+ ShadowIntentFilter that = (ShadowIntentFilter) o;
+
+ return actions.equals( that.actions ) && categories.equals( that.categories )
+ && schemes.equals( that.schemes ) && authoritites.equals( that.authoritites );
+ }
+
+ @Override @Implementation
+ public int hashCode() {
+ int result = 13;
+ result = 31 * result + actions.hashCode();
+ result = 31 * result + categories.hashCode();
+ result = 31 * result + schemes.hashCode();
+ result = 31 * result + authoritites.hashCode();
+ return result;
+ }
}
Oops, something went wrong.

0 comments on commit fb8daf7

Please sign in to comment.