1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dumprendertree; 18 19 import com.android.dumprendertree.TestShellActivity.DumpDataType; 20 import com.android.dumprendertree.forwarder.AdbUtils; 21 import com.android.dumprendertree.forwarder.ForwardService; 22 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Environment; 26 import android.test.ActivityInstrumentationTestCase2; 27 import android.util.Log; 28 29 import java.io.BufferedOutputStream; 30 import java.io.BufferedReader; 31 import java.io.File; 32 import java.io.FileNotFoundException; 33 import java.io.FileOutputStream; 34 import java.io.FileReader; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.util.Vector; 39 40 // TestRecorder creates four files ... 41 // - passing tests 42 // - failing tests 43 // - tests for which results are ignored 44 // - tests with no text results available 45 // TestRecorder does not have the ability to clear the results. 46 class MyTestRecorder { 47 private BufferedOutputStream mBufferedOutputPassedStream; 48 private BufferedOutputStream mBufferedOutputFailedStream; 49 private BufferedOutputStream mBufferedOutputIgnoreResultStream; 50 private BufferedOutputStream mBufferedOutputNoResultStream; 51 passed(String layout_file)52 public void passed(String layout_file) { 53 try { 54 mBufferedOutputPassedStream.write(layout_file.getBytes()); 55 mBufferedOutputPassedStream.write('\n'); 56 mBufferedOutputPassedStream.flush(); 57 } catch(Exception e) { 58 e.printStackTrace(); 59 } 60 } 61 failed(String layout_file)62 public void failed(String layout_file) { 63 try { 64 mBufferedOutputFailedStream.write(layout_file.getBytes()); 65 mBufferedOutputFailedStream.write('\n'); 66 mBufferedOutputFailedStream.flush(); 67 } catch(Exception e) { 68 e.printStackTrace(); 69 } 70 } 71 ignoreResult(String layout_file)72 public void ignoreResult(String layout_file) { 73 try { 74 mBufferedOutputIgnoreResultStream.write(layout_file.getBytes()); 75 mBufferedOutputIgnoreResultStream.write('\n'); 76 mBufferedOutputIgnoreResultStream.flush(); 77 } catch(Exception e) { 78 e.printStackTrace(); 79 } 80 } 81 noResult(String layout_file)82 public void noResult(String layout_file) { 83 try { 84 mBufferedOutputNoResultStream.write(layout_file.getBytes()); 85 mBufferedOutputNoResultStream.write('\n'); 86 mBufferedOutputNoResultStream.flush(); 87 } catch(Exception e) { 88 e.printStackTrace(); 89 } 90 } 91 MyTestRecorder(boolean resume)92 public MyTestRecorder(boolean resume) { 93 try { 94 File externalDir = Environment.getExternalStorageDirectory(); 95 File resultsPassedFile = new File(externalDir, "layout_tests_passed.txt"); 96 File resultsFailedFile = new File(externalDir, "layout_tests_failed.txt"); 97 File resultsIgnoreResultFile = new File(externalDir, "layout_tests_ignored.txt"); 98 File noExpectedResultFile = new File(externalDir, "layout_tests_nontext.txt"); 99 100 mBufferedOutputPassedStream = 101 new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume)); 102 mBufferedOutputFailedStream = 103 new BufferedOutputStream(new FileOutputStream(resultsFailedFile, resume)); 104 mBufferedOutputIgnoreResultStream = 105 new BufferedOutputStream(new FileOutputStream(resultsIgnoreResultFile, resume)); 106 mBufferedOutputNoResultStream = 107 new BufferedOutputStream(new FileOutputStream(noExpectedResultFile, resume)); 108 } catch (Exception e) { 109 e.printStackTrace(); 110 } 111 } 112 close()113 public void close() { 114 try { 115 mBufferedOutputPassedStream.close(); 116 mBufferedOutputFailedStream.close(); 117 mBufferedOutputIgnoreResultStream.close(); 118 mBufferedOutputNoResultStream.close(); 119 } catch (Exception e) { 120 e.printStackTrace(); 121 } 122 } 123 } 124 125 126 public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> { 127 128 private static final String LOGTAG = "LayoutTests"; 129 static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000; 130 131 static final String EXTERNAL_DIR = Environment.getExternalStorageDirectory().toString(); 132 static final String LAYOUT_TESTS_ROOT = EXTERNAL_DIR + "/webkit/layout_tests/"; 133 static final String LAYOUT_TESTS_RESULT_DIR = EXTERNAL_DIR + "/webkit/layout_tests_results/"; 134 static final String ANDROID_EXPECTED_RESULT_DIR = EXTERNAL_DIR + "/webkit/expected_results/"; 135 static final String LAYOUT_TESTS_LIST_FILE = EXTERNAL_DIR + "/webkit/layout_tests_list.txt"; 136 static final String TEST_STATUS_FILE = EXTERNAL_DIR + "/webkit/running_test.txt"; 137 static final String LAYOUT_TESTS_RESULTS_REFERENCE_FILES[] = { 138 "results/layout_tests_passed.txt", 139 "results/layout_tests_failed.txt", 140 "results/layout_tests_nontext.txt", 141 "results/layout_tests_crashed.txt", 142 "run_layout_tests.py" 143 }; 144 145 static final String LAYOUT_RESULTS_FAILED_RESULT_FILE = "results/layout_tests_failed.txt"; 146 static final String LAYOUT_RESULTS_NONTEXT_RESULT_FILE = "results/layout_tests_nontext.txt"; 147 static final String LAYOUT_RESULTS_CRASHED_RESULT_FILE = "results/layout_tests_crashed.txt"; 148 static final String LAYOUT_TESTS_RUNNER = "run_layout_tests.py"; 149 150 private MyTestRecorder mResultRecorder; 151 private Vector<String> mTestList; 152 // Whether we should ignore the result for the corresponding test. Ordered same as mTestList. 153 private Vector<Boolean> mTestListIgnoreResult; 154 private boolean mRebaselineResults; 155 // The JavaScript engine currently in use. This determines which set of Android-specific 156 // expected test results we use. 157 private String mJsEngine; 158 private String mTestPathPrefix; 159 private boolean mFinished; 160 private int mTestCount; 161 private int mResumeIndex; 162 LayoutTestsAutoTest()163 public LayoutTestsAutoTest() { 164 super(TestShellActivity.class); 165 } 166 getTestList()167 private void getTestList() { 168 // Read test list. 169 try { 170 BufferedReader inReader = new BufferedReader(new FileReader(LAYOUT_TESTS_LIST_FILE)); 171 String line = inReader.readLine(); 172 while (line != null) { 173 if (line.startsWith(mTestPathPrefix)) { 174 String[] components = line.split(" "); 175 mTestList.add(components[0]); 176 mTestListIgnoreResult.add(components.length > 1 && components[1].equals("IGNORE_RESULT")); 177 } 178 line = inReader.readLine(); 179 } 180 inReader.close(); 181 Log.v(LOGTAG, "Test list has " + mTestList.size() + " test(s)."); 182 } catch (Exception e) { 183 Log.e(LOGTAG, "Error while reading test list : " + e.getMessage()); 184 } 185 mTestCount = mTestList.size(); 186 } 187 resumeTestList()188 private void resumeTestList() { 189 // read out the test name it stoped last time. 190 try { 191 String line = FsUtils.readTestStatus(TEST_STATUS_FILE); 192 for (int i = 0; i < mTestList.size(); i++) { 193 if (mTestList.elementAt(i).equals(line)) { 194 mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size())); 195 mTestListIgnoreResult = new Vector<Boolean>(mTestListIgnoreResult.subList(i+1, mTestListIgnoreResult.size())); 196 mResumeIndex = i + 1; 197 break; 198 } 199 } 200 } catch (Exception e) { 201 Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE); 202 } 203 } 204 clearTestStatus()205 private void clearTestStatus() { 206 // Delete TEST_STATUS_FILE 207 try { 208 File f = new File(TEST_STATUS_FILE); 209 if (f.delete()) 210 Log.v(LOGTAG, "Deleted " + TEST_STATUS_FILE); 211 else 212 Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE); 213 } catch (Exception e) { 214 Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage()); 215 } 216 } 217 getResultFile(String test)218 private String getResultFile(String test) { 219 String shortName = test.substring(0, test.lastIndexOf('.')); 220 // Write actual results to result directory. 221 return shortName.replaceFirst(LAYOUT_TESTS_ROOT, LAYOUT_TESTS_RESULT_DIR) + "-result.txt"; 222 } 223 224 // Gets the file which contains WebKit's expected results for this test. getExpectedResultFile(String test)225 private String getExpectedResultFile(String test) { 226 // The generic result is at <path>/<name>-expected.txt 227 // First try the Android-specific result at 228 // platform/android-<js-engine>/<path>/<name>-expected.txt 229 // then 230 // platform/android/<path>/<name>-expected.txt 231 int pos = test.lastIndexOf('.'); 232 if (pos == -1) 233 return null; 234 String genericExpectedResult = test.substring(0, pos) + "-expected.txt"; 235 String androidExpectedResultsDir = "platform/android-" + mJsEngine + "/"; 236 String androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT, 237 LAYOUT_TESTS_ROOT + androidExpectedResultsDir); 238 File f = new File(androidExpectedResult); 239 if (f.exists()) 240 return androidExpectedResult; 241 androidExpectedResultsDir = "platform/android/"; 242 androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT, 243 LAYOUT_TESTS_ROOT + androidExpectedResultsDir); 244 f = new File(androidExpectedResult); 245 return f.exists() ? androidExpectedResult : genericExpectedResult; 246 } 247 248 // Gets the file which contains the actual results of running the test on 249 // Android, generated by a previous run which set a new baseline. getAndroidExpectedResultFile(String expectedResultFile)250 private String getAndroidExpectedResultFile(String expectedResultFile) { 251 return expectedResultFile.replaceFirst(LAYOUT_TESTS_ROOT, ANDROID_EXPECTED_RESULT_DIR); 252 } 253 254 // Wrap up failedCase(String file)255 private void failedCase(String file) { 256 Log.w("Layout test: ", file + " failed"); 257 mResultRecorder.failed(file); 258 } 259 passedCase(String file)260 private void passedCase(String file) { 261 Log.v("Layout test:", file + " passed"); 262 mResultRecorder.passed(file); 263 } 264 ignoreResultCase(String file)265 private void ignoreResultCase(String file) { 266 Log.v("Layout test:", file + " ignore result"); 267 mResultRecorder.ignoreResult(file); 268 } 269 noResultCase(String file)270 private void noResultCase(String file) { 271 Log.v("Layout test:", file + " no expected result"); 272 mResultRecorder.noResult(file); 273 } 274 processResult(String testFile, String actualResultFile, String expectedResultFile, boolean ignoreResult)275 private void processResult(String testFile, String actualResultFile, String expectedResultFile, boolean ignoreResult) { 276 Log.v(LOGTAG, " Processing result: " + testFile); 277 278 if (ignoreResult) { 279 ignoreResultCase(testFile); 280 return; 281 } 282 283 File actual = new File(actualResultFile); 284 File expected = new File(expectedResultFile); 285 if (actual.exists() && expected.exists()) { 286 try { 287 if (FsUtils.diffIgnoreSpaces(actualResultFile, expectedResultFile)) { 288 passedCase(testFile); 289 } else { 290 failedCase(testFile); 291 } 292 } catch (FileNotFoundException ex) { 293 Log.e(LOGTAG, "File not found : " + ex.getMessage()); 294 } catch (IOException ex) { 295 Log.e(LOGTAG, "IO Error : " + ex.getMessage()); 296 } 297 return; 298 } 299 300 if (!expected.exists()) { 301 noResultCase(testFile); 302 } 303 } 304 runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout, boolean ignoreResult, int testNumber)305 private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout, boolean ignoreResult, int testNumber) { 306 activity.setCallback(new TestShellCallback() { 307 public void finished() { 308 synchronized (LayoutTestsAutoTest.this) { 309 mFinished = true; 310 LayoutTestsAutoTest.this.notifyAll(); 311 } 312 } 313 314 public void timedOut(String url) { 315 Log.v(LOGTAG, "layout timeout: " + url); 316 } 317 }); 318 319 String resultFile = getResultFile(test); 320 if (resultFile == null) { 321 // Simply ignore this test. 322 return; 323 } 324 if (mRebaselineResults) { 325 String expectedResultFile = getExpectedResultFile(test); 326 File f = new File(expectedResultFile); 327 if (f.exists()) { 328 return; // don't run test and don't overwrite default tests. 329 } 330 331 resultFile = getAndroidExpectedResultFile(expectedResultFile); 332 } 333 334 mFinished = false; 335 Intent intent = new Intent(Intent.ACTION_VIEW); 336 intent.setClass(activity, TestShellActivity.class); 337 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 338 intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(test)); 339 intent.putExtra(TestShellActivity.RESULT_FILE, resultFile); 340 intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout); 341 intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, mTestCount); 342 intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, testNumber); 343 intent.putExtra(TestShellActivity.STOP_ON_REF_ERROR, true); 344 activity.startActivity(intent); 345 346 // Wait until done. 347 synchronized (this) { 348 while(!mFinished){ 349 try { 350 this.wait(); 351 } catch (InterruptedException e) { } 352 } 353 } 354 355 if (!mRebaselineResults) { 356 String expectedResultFile = getExpectedResultFile(test); 357 File f = new File(expectedResultFile); 358 if (!f.exists()) { 359 expectedResultFile = getAndroidExpectedResultFile(expectedResultFile); 360 } 361 362 processResult(test, resultFile, expectedResultFile, ignoreResult); 363 } 364 } 365 366 // Invokes running of layout tests 367 // and waits till it has finished running. executeLayoutTests(boolean resume)368 public void executeLayoutTests(boolean resume) { 369 LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); 370 // A convenient method to be called by another activity. 371 372 if (runner.mTestPath == null) { 373 Log.e(LOGTAG, "No test specified"); 374 return; 375 } 376 377 this.mTestList = new Vector<String>(); 378 this.mTestListIgnoreResult = new Vector<Boolean>(); 379 380 // Read settings 381 mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath(); 382 mRebaselineResults = runner.mRebaseline; 383 // V8 is the default JavaScript engine. 384 mJsEngine = runner.mJsEngine == null ? "v8" : runner.mJsEngine; 385 386 int timeout = runner.mTimeoutInMillis; 387 if (timeout <= 0) { 388 timeout = DEFAULT_TIMEOUT_IN_MILLIS; 389 } 390 391 this.mResultRecorder = new MyTestRecorder(resume); 392 393 if (!resume) 394 clearTestStatus(); 395 396 getTestList(); 397 if (resume) 398 resumeTestList(); 399 400 TestShellActivity activity = getActivity(); 401 activity.setDefaultDumpDataType(DumpDataType.EXT_REPR); 402 403 // Run tests. 404 for (int i = 0; i < mTestList.size(); i++) { 405 String s = mTestList.elementAt(i); 406 boolean ignoreResult = mTestListIgnoreResult.elementAt(i); 407 FsUtils.updateTestStatus(TEST_STATUS_FILE, s); 408 // Run tests 409 // i is 0 based, but test count is 1 based so add 1 to i here. 410 runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult, 411 i + 1 + mResumeIndex); 412 } 413 414 FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE"); 415 ForwardService.getForwardService().stopForwardService(); 416 activity.finish(); 417 } 418 getTestPath()419 private String getTestPath() { 420 LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); 421 422 String test_path = LAYOUT_TESTS_ROOT; 423 if (runner.mTestPath != null) { 424 test_path += runner.mTestPath; 425 } 426 test_path = new File(test_path).getAbsolutePath(); 427 Log.v("LayoutTestsAutoTest", " Test path : " + test_path); 428 429 return test_path; 430 } 431 generateTestList()432 public void generateTestList() { 433 try { 434 File tests_list = new File(LAYOUT_TESTS_LIST_FILE); 435 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false)); 436 FsUtils.writeLayoutTestListRecursively(bos, getTestPath(), false); // Don't ignore results 437 bos.flush(); 438 bos.close(); 439 } catch (Exception e) { 440 Log.e(LOGTAG, "Error when creating test list: " + e.getMessage()); 441 } 442 } 443 444 // Running all the layout tests at once sometimes 445 // causes the dumprendertree to run out of memory. 446 // So, additional tests are added to run the tests 447 // in chunks. startLayoutTests()448 public void startLayoutTests() { 449 try { 450 File tests_list = new File(LAYOUT_TESTS_LIST_FILE); 451 if (!tests_list.exists()) 452 generateTestList(); 453 } catch (Exception e) { 454 e.printStackTrace(); 455 } 456 457 executeLayoutTests(false); 458 } 459 resumeLayoutTests()460 public void resumeLayoutTests() { 461 executeLayoutTests(true); 462 } 463 copyResultsAndRunnerAssetsToCache()464 public void copyResultsAndRunnerAssetsToCache() { 465 try { 466 Context targetContext = getInstrumentation().getTargetContext(); 467 File cacheDir = targetContext.getCacheDir(); 468 469 for( int i=0; i< LAYOUT_TESTS_RESULTS_REFERENCE_FILES.length; i++) { 470 InputStream in = targetContext.getAssets().open( 471 LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i]); 472 OutputStream out = new FileOutputStream(new File(cacheDir, 473 LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i])); 474 475 byte[] buf = new byte[2048]; 476 int len; 477 478 while ((len = in.read(buf)) >= 0 ) { 479 out.write(buf, 0, len); 480 } 481 out.close(); 482 in.close(); 483 } 484 }catch (IOException e) { 485 e.printStackTrace(); 486 } 487 488 } 489 490 } 491