1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package android.jvmti.cts;
15 
16 import java.io.BufferedReader;
17 import java.io.ByteArrayInputStream;
18 import java.io.ByteArrayOutputStream;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.io.PrintStream;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.InvocationTargetException;
26 
27 import android.content.pm.PackageManager;
28 import android.util.Log;
29 
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 
34 /**
35  * Check redefineClasses-related functionality.
36  */
37 public class JvmtiRunTestBasedTest extends JvmtiTestBase {
38 
39     private PrintStream oldOut, oldErr;
40     private ByteArrayOutputStream bufferedOut, bufferedErr;
41 
42     private class TeeLogcatOutputStream extends OutputStream {
43         private OutputStream os;
44         private ByteArrayOutputStream lc_os;
TeeLogcatOutputStream(OutputStream os)45         public TeeLogcatOutputStream(OutputStream os) {
46             this.lc_os = new ByteArrayOutputStream();
47             this.os = os;
48         }
write(int b)49         public void write(int b) throws IOException {
50             os.write(b);
51             if (b == (int)'\n') {
52               lc_os.flush();
53               Log.i(mActivity.getPackageName(), "Test Output: " +  lc_os.toString());
54               lc_os.reset();
55             } else {
56               lc_os.write(b);
57             }
58         }
close()59         public void close() throws IOException {
60             flush();
61             os.close();
62             lc_os.close();
63         }
flush()64         public void flush() throws IOException {
65             os.flush();
66             lc_os.flush();
67         }
68     }
69 
70     @Before
setUp()71     public void setUp() throws Exception {
72         oldOut = System.out;
73         oldErr = System.err;
74         bufferedOut = new ByteArrayOutputStream();
75         bufferedErr = new ByteArrayOutputStream();
76 
77         if (doExtraLogging()) {
78             setupExtraLogging();
79             System.setOut(new PrintStream(new TeeLogcatOutputStream(bufferedOut), true));
80             System.setErr(new PrintStream(new TeeLogcatOutputStream(bufferedErr), true));
81         } else {
82             System.setOut(new PrintStream(bufferedOut, true));
83             System.setErr(new PrintStream(bufferedErr, true));
84         }
85     }
86 
87     @After
tearDown()88     public void tearDown() {
89         System.setOut(oldOut);
90         System.setErr(oldErr);
91     }
92 
setupExtraLogging()93     private void setupExtraLogging() {
94       setupExtraLogging("plugin,deopt,jdwp,jit,agents,threads");
95     }
96 
setupExtraLogging(String arg)97     private native void setupExtraLogging(String arg);
98 
doExtraLogging()99     protected boolean doExtraLogging() throws Exception {
100         return mActivity
101             .getPackageManager()
102             .getApplicationInfo(mActivity.getPackageName(), PackageManager.GET_META_DATA)
103             .metaData
104             .getBoolean("android.jvmti.cts.run_test.extra_logging", /*defaultValue*/false);
105     }
106 
getTestNumber()107     protected int getTestNumber() throws Exception {
108         return mActivity.getPackageManager().getApplicationInfo(mActivity.getPackageName(),
109                 PackageManager.GET_META_DATA).metaData.getInt("android.jvmti.cts.run_test_nr");
110     }
111 
112     // Some tests are very sensitive to state of the thread they are running on. To support this we
113     // can have tests run on newly created threads. This defaults to false.
needNewThread()114     protected boolean needNewThread() throws Exception {
115         return mActivity
116             .getPackageManager()
117             .getApplicationInfo(mActivity.getPackageName(), PackageManager.GET_META_DATA)
118             .metaData
119             .getBoolean("android.jvmti.cts.needs_new_thread", /*defaultValue*/false);
120     }
121 
122     @Test
testRunTest()123     public void testRunTest() throws Exception {
124         final int nr = getTestNumber();
125 
126         // Load the test class.
127         Class<?> testClass = Class.forName("art.Test" + nr);
128         final Method runMethod = testClass.getDeclaredMethod("run");
129         if (needNewThread()) {
130           // Make sure the thread the test is running on has the right name. Some tests are
131           // sensitive to this. Ideally we would also avoid having a try-catch too but that is more
132           // trouble than it's worth.
133           final Throwable[] final_throw = new Throwable[] { null };
134           Thread main_thread = new Thread(
135               () -> {
136                 try {
137                   runMethod.invoke(null);
138                 } catch (IllegalArgumentException e) {
139                   throw new Error("Exception thrown", e);
140                 } catch (InvocationTargetException e) {
141                   throw new Error("Exception thrown", e);
142                 } catch (NullPointerException e) {
143                   throw new Error("Exception thrown", e);
144                 } catch (IllegalAccessException e) {
145                   throw new Error("Exception thrown", e);
146                 }
147               }, "main");
148           main_thread.setUncaughtExceptionHandler((thread, e) -> { final_throw[0] = e; });
149 
150           main_thread.start();
151           main_thread.join();
152 
153           if (final_throw[0] != null) {
154             throw new InvocationTargetException(final_throw[0], "Remote exception occurred.");
155           }
156         } else {
157           runMethod.invoke(null);
158         }
159 
160         // Load the expected txt file.
161         InputStream expectedStream = getClass().getClassLoader()
162                 .getResourceAsStream("results." + nr + ".expected.txt");
163         compare(expectedStream, bufferedOut);
164 
165         if (bufferedErr.size() > 0) {
166             throw new IllegalStateException(
167                     "Did not expect System.err output: " + bufferedErr.toString());
168         }
169     }
170 
171     // Very primitive diff. Doesn't do any smart things...
compare(InputStream expectedStream, ByteArrayOutputStream resultStream)172     private void compare(InputStream expectedStream, ByteArrayOutputStream resultStream)
173             throws Exception {
174         // This isn't really optimal in any way.
175         BufferedReader expectedReader = new BufferedReader(new InputStreamReader(expectedStream));
176         BufferedReader resultReader = new BufferedReader(
177                 new InputStreamReader(new ByteArrayInputStream(resultStream.toByteArray())));
178         StringBuilder resultBuilder = new StringBuilder();
179         boolean failed = false;
180         for (;;) {
181             String expString = expectedReader.readLine();
182             String resString = resultReader.readLine();
183 
184             if (expString == null && resString == null) {
185                 // Done.
186                 break;
187             }
188 
189             if (expString != null && resString != null && expString.equals(resString)) {
190                 resultBuilder.append("  ");
191                 resultBuilder.append(expString);
192                 resultBuilder.append('\n');
193                 continue;
194             }
195 
196             failed = true;
197             if (expString != null) {
198                 resultBuilder.append("- ");
199                 resultBuilder.append(expString);
200                 resultBuilder.append('\n');
201             }
202             if (resString != null) {
203                 resultBuilder.append("+ ");
204                 resultBuilder.append(resString);
205                 resultBuilder.append('\n');
206             }
207         }
208         if (failed) {
209             throw new IllegalStateException(resultBuilder.toString());
210         }
211     }
212 }
213