• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.interceptors;
2 
3 import static com.google.common.truth.Truth.assertThat;
4 
5 import android.os.SystemClock;
6 import androidx.test.ext.junit.runners.AndroidJUnit4;
7 import java.io.ByteArrayOutputStream;
8 import java.io.FileDescriptor;
9 import java.io.PrintStream;
10 import java.lang.invoke.CallSite;
11 import java.lang.invoke.MethodHandles;
12 import java.lang.invoke.MethodType;
13 import java.lang.reflect.Field;
14 import java.net.Socket;
15 import java.time.Duration;
16 import java.util.Arrays;
17 import java.util.regex.Pattern;
18 import java.util.stream.Collectors;
19 import org.junit.Test;
20 import org.junit.runner.RunWith;
21 import org.robolectric.annotation.LooperMode;
22 import org.robolectric.internal.bytecode.InvokeDynamicSupport;
23 import org.robolectric.shadows.ShadowLooper;
24 import org.robolectric.shadows.ShadowSystemClock;
25 import org.robolectric.util.ReflectionHelpers.ClassParameter;
26 
27 /** Integration tests for Android interceptors. */
28 @RunWith(AndroidJUnit4.class)
29 public class AndroidInterceptorsIntegrationTest {
30 
31   @Test
systemLog_shouldWriteToStderr()32   public void systemLog_shouldWriteToStderr() throws Throwable {
33     PrintStream stderr = System.err;
34     ByteArrayOutputStream stream = new ByteArrayOutputStream();
35     PrintStream printStream = new PrintStream(stream);
36     System.setErr(printStream);
37     try {
38       for (String methodName : new String[] {"logE", "logW"}) {
39         stream.reset();
40         // One parameter: [message]
41         invokeDynamic(
42             System.class, methodName, void.class, ClassParameter.from(String.class, "hello"));
43         String expected1 = String.format("System\\.%s: hello", methodName);
44         // Two parameters: [message, throwable]
45         // We verify that the stack trace is dumped by looking for the name of this method.
46         invokeDynamic(
47             System.class,
48             methodName,
49             void.class,
50             ClassParameter.from(String.class, "world"),
51             ClassParameter.from(Throwable.class, new Throwable("message")));
52         String expected2 =
53             String.format(
54                 "System.%s: world.*java\\.lang\\.Throwable: message.*"
55                     + "at .*AndroidInterceptorsIntegrationTest\\.systemLog_shouldWriteToStderr",
56                 methodName);
57         // Due to the possibility of running tests in Parallel, assertions checking stderr contents
58         // should not assert equality.
59         assertThat(stream.toString())
60             .matches(Pattern.compile(".*" + expected1 + ".*" + expected2 + ".*", Pattern.DOTALL));
61       }
62     } finally {
63       System.setErr(stderr);
64     }
65   }
66 
67   @Test
systemNanoTime_shouldReturnShadowClockTime()68   public void systemNanoTime_shouldReturnShadowClockTime() throws Throwable {
69     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
70       ShadowSystemClock.setNanoTime(Duration.ofMillis(200).toNanos());
71     } else {
72       SystemClock.setCurrentTimeMillis(200);
73     }
74 
75     long nanoTime = invokeDynamic(System.class, "nanoTime", long.class);
76     assertThat(nanoTime).isEqualTo(Duration.ofMillis(200).toNanos());
77   }
78 
79   @Test
systemCurrentTimeMillis_shouldReturnShadowClockTime()80   public void systemCurrentTimeMillis_shouldReturnShadowClockTime() throws Throwable {
81     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
82       ShadowSystemClock.setNanoTime(Duration.ofMillis(200).toNanos());
83     } else {
84       SystemClock.setCurrentTimeMillis(200);
85     }
86 
87     long currentTimeMillis = invokeDynamic(System.class, "currentTimeMillis", long.class);
88     assertThat(currentTimeMillis).isEqualTo(200);
89   }
90 
91   /* Creates a FileDescriptor-object using reflection. Note that the "fd"-field is present for
92    * both the Windows and Unix implementations of FileDescriptor in OpenJDK.
93    */
createFd(int fd)94   private static FileDescriptor createFd(int fd) throws Throwable {
95     FileDescriptor ret = new FileDescriptor();
96     Field accessor = FileDescriptor.class.getDeclaredField("fd");
97     accessor.setAccessible(true);
98     accessor.set(ret, fd);
99     return ret;
100   }
101 
102   @Test
fileDescriptorRelease_isValid_correctResultsAfterRelease()103   public void fileDescriptorRelease_isValid_correctResultsAfterRelease() throws Throwable {
104     FileDescriptor original = createFd(42);
105     assertThat(original.valid()).isTrue();
106     FileDescriptor copy =
107         invokeDynamic(
108             FileDescriptor.class,
109             "release$",
110             FileDescriptor.class,
111             ClassParameter.from(FileDescriptor.class, original));
112     assertThat(copy.valid()).isTrue();
113     assertThat(original.valid()).isFalse();
114   }
115 
116   @Test
fileDescriptorRelease_allowsReleaseOnInvalidFd()117   public void fileDescriptorRelease_allowsReleaseOnInvalidFd() throws Throwable {
118     FileDescriptor original = new FileDescriptor();
119     assertThat(original.valid()).isFalse();
120     FileDescriptor copy =
121         invokeDynamic(
122             FileDescriptor.class,
123             "release$",
124             FileDescriptor.class,
125             ClassParameter.from(FileDescriptor.class, original));
126     assertThat(copy.valid()).isFalse();
127     assertThat(original.valid()).isFalse();
128   }
129 
130   @Test
fileDescriptorRelease_doubleReleaseReturnsInvalidFd()131   public void fileDescriptorRelease_doubleReleaseReturnsInvalidFd() throws Throwable {
132     FileDescriptor original = createFd(42);
133     assertThat(original.valid()).isTrue();
134     FileDescriptor copy =
135         invokeDynamic(
136             FileDescriptor.class,
137             "release$",
138             FileDescriptor.class,
139             ClassParameter.from(FileDescriptor.class, original));
140     FileDescriptor copy2 =
141         invokeDynamic(
142             FileDescriptor.class,
143             "release$",
144             FileDescriptor.class,
145             ClassParameter.from(FileDescriptor.class, original));
146     assertThat(copy.valid()).isTrue();
147     assertThat(copy2.valid()).isFalse();
148     assertThat(original.valid()).isFalse();
149   }
150 
151   @Test
fileDescriptorRelease_releaseFdCorrect()152   public void fileDescriptorRelease_releaseFdCorrect() throws Throwable {
153     FileDescriptor original = createFd(42);
154     FileDescriptor copy =
155         invokeDynamic(
156             FileDescriptor.class,
157             "release$",
158             FileDescriptor.class,
159             ClassParameter.from(FileDescriptor.class, original));
160     Field accessor = FileDescriptor.class.getDeclaredField("fd");
161     accessor.setAccessible(true);
162     assertThat(accessor.get(copy)).isEqualTo(42);
163   }
164 
165   @Test
socketFileDescriptor_returnsNullFileDescriptor()166   public void socketFileDescriptor_returnsNullFileDescriptor() throws Throwable {
167     FileDescriptor fd =
168         invokeDynamic(
169             Socket.class,
170             "getFileDescriptor$",
171             void.class,
172             ClassParameter.from(Socket.class, new Socket()));
173     assertThat(fd).isNull();
174   }
175 
176   @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
invokeDynamic( Class<?> cls, String methodName, Class<?> returnType, ClassParameter<?>... params)177   private static <T> T invokeDynamic(
178       Class<?> cls, String methodName, Class<?> returnType, ClassParameter<?>... params)
179       throws Throwable {
180     MethodType methodType =
181         MethodType.methodType(
182             returnType, Arrays.stream(params).map(param -> param.clazz).toArray(Class[]::new));
183     CallSite callsite =
184         InvokeDynamicSupport.bootstrapIntrinsic(
185             MethodHandles.lookup(), methodName, methodType, cls.getName());
186     return (T)
187         callsite
188             .dynamicInvoker()
189             .invokeWithArguments(
190                 Arrays.stream(params).map(param -> param.value).collect(Collectors.toList()));
191   }
192 }
193