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 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 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 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 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 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 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 163 public LayoutTestsAutoTest() { 164 super(TestShellActivity.class); 165 } 166 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 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 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 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. 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. 250 private String getAndroidExpectedResultFile(String expectedResultFile) { 251 return expectedResultFile.replaceFirst(LAYOUT_TESTS_ROOT, ANDROID_EXPECTED_RESULT_DIR); 252 } 253 254 // Wrap up 255 private void failedCase(String file) { 256 Log.w("Layout test: ", file + " failed"); 257 mResultRecorder.failed(file); 258 } 259 260 private void passedCase(String file) { 261 Log.v("Layout test:", file + " passed"); 262 mResultRecorder.passed(file); 263 } 264 265 private void ignoreResultCase(String file) { 266 Log.v("Layout test:", file + " ignore result"); 267 mResultRecorder.ignoreResult(file); 268 } 269 270 private void noResultCase(String file) { 271 Log.v("Layout test:", file + " no expected result"); 272 mResultRecorder.noResult(file); 273 } 274 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 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 @Override 319 public void dumpResult(String webViewDump) { 320 } 321 }); 322 323 String resultFile = getResultFile(test); 324 if (resultFile == null) { 325 // Simply ignore this test. 326 return; 327 } 328 if (mRebaselineResults) { 329 String expectedResultFile = getExpectedResultFile(test); 330 File f = new File(expectedResultFile); 331 if (f.exists()) { 332 return; // don't run test and don't overwrite default tests. 333 } 334 335 resultFile = getAndroidExpectedResultFile(expectedResultFile); 336 } 337 338 mFinished = false; 339 Intent intent = new Intent(Intent.ACTION_VIEW); 340 intent.setClass(activity, TestShellActivity.class); 341 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 342 intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(test)); 343 intent.putExtra(TestShellActivity.RESULT_FILE, resultFile); 344 intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout); 345 intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, mTestCount); 346 intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, testNumber); 347 intent.putExtra(TestShellActivity.STOP_ON_REF_ERROR, true); 348 activity.startActivity(intent); 349 350 // Wait until done. 351 synchronized (this) { 352 while(!mFinished){ 353 try { 354 this.wait(); 355 } catch (InterruptedException e) { } 356 } 357 } 358 359 if (!mRebaselineResults) { 360 String expectedResultFile = getExpectedResultFile(test); 361 File f = new File(expectedResultFile); 362 if (!f.exists()) { 363 expectedResultFile = getAndroidExpectedResultFile(expectedResultFile); 364 } 365 366 processResult(test, resultFile, expectedResultFile, ignoreResult); 367 } 368 } 369 370 // Invokes running of layout tests 371 // and waits till it has finished running. 372 public void executeLayoutTests(boolean resume) { 373 LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); 374 // A convenient method to be called by another activity. 375 376 if (runner.mTestPath == null) { 377 Log.e(LOGTAG, "No test specified"); 378 return; 379 } 380 381 this.mTestList = new Vector<String>(); 382 this.mTestListIgnoreResult = new Vector<Boolean>(); 383 384 // Read settings 385 mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath(); 386 mRebaselineResults = runner.mRebaseline; 387 // V8 is the default JavaScript engine. 388 mJsEngine = runner.mJsEngine == null ? "v8" : runner.mJsEngine; 389 390 int timeout = runner.mTimeoutInMillis; 391 if (timeout <= 0) { 392 timeout = DEFAULT_TIMEOUT_IN_MILLIS; 393 } 394 395 this.mResultRecorder = new MyTestRecorder(resume); 396 397 if (!resume) 398 clearTestStatus(); 399 400 getTestList(); 401 if (resume) 402 resumeTestList(); 403 404 TestShellActivity activity = getActivity(); 405 activity.setDefaultDumpDataType(DumpDataType.EXT_REPR); 406 407 // Run tests. 408 for (int i = 0; i < mTestList.size(); i++) { 409 String s = mTestList.elementAt(i); 410 boolean ignoreResult = mTestListIgnoreResult.elementAt(i); 411 FsUtils.updateTestStatus(TEST_STATUS_FILE, s); 412 // Run tests 413 // i is 0 based, but test count is 1 based so add 1 to i here. 414 runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult, 415 i + 1 + mResumeIndex); 416 } 417 418 FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE"); 419 ForwardService.getForwardService().stopForwardService(); 420 activity.finish(); 421 } 422 423 private String getTestPath() { 424 LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); 425 426 String test_path = LAYOUT_TESTS_ROOT; 427 if (runner.mTestPath != null) { 428 test_path += runner.mTestPath; 429 } 430 test_path = new File(test_path).getAbsolutePath(); 431 Log.v("LayoutTestsAutoTest", " Test path : " + test_path); 432 433 return test_path; 434 } 435 436 public void generateTestList() { 437 try { 438 File tests_list = new File(LAYOUT_TESTS_LIST_FILE); 439 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false)); 440 FsUtils.writeLayoutTestListRecursively(bos, getTestPath(), false); // Don't ignore results 441 bos.flush(); 442 bos.close(); 443 } catch (Exception e) { 444 Log.e(LOGTAG, "Error when creating test list: " + e.getMessage()); 445 } 446 } 447 448 // Running all the layout tests at once sometimes 449 // causes the dumprendertree to run out of memory. 450 // So, additional tests are added to run the tests 451 // in chunks. 452 public void startLayoutTests() { 453 try { 454 File tests_list = new File(LAYOUT_TESTS_LIST_FILE); 455 if (!tests_list.exists()) 456 generateTestList(); 457 } catch (Exception e) { 458 e.printStackTrace(); 459 } 460 461 executeLayoutTests(false); 462 } 463 464 public void resumeLayoutTests() { 465 executeLayoutTests(true); 466 } 467 468 public void copyResultsAndRunnerAssetsToCache() { 469 try { 470 Context targetContext = getInstrumentation().getTargetContext(); 471 File cacheDir = targetContext.getCacheDir(); 472 473 for( int i=0; i< LAYOUT_TESTS_RESULTS_REFERENCE_FILES.length; i++) { 474 InputStream in = targetContext.getAssets().open( 475 LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i]); 476 OutputStream out = new FileOutputStream(new File(cacheDir, 477 LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i])); 478 479 byte[] buf = new byte[2048]; 480 int len; 481 482 while ((len = in.read(buf)) >= 0 ) { 483 out.write(buf, 0, len); 484 } 485 out.close(); 486 in.close(); 487 } 488 }catch (IOException e) { 489 e.printStackTrace(); 490 } 491 492 } 493 494 } 495