1 /* 2 * Copyright (C) 2021 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 package com.android.tradefed.device; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertTrue; 23 24 import com.android.ddmlib.IDevice; 25 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 26 import com.android.tradefed.testtype.IDeviceTest; 27 import com.android.tradefed.util.CommandResult; 28 import com.android.tradefed.util.CommandStatus; 29 import com.android.tradefed.util.FileUtil; 30 import com.android.tradefed.util.StreamUtil; 31 32 import org.junit.Before; 33 import org.junit.Test; 34 import org.junit.runner.RunWith; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Random; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 49 /** Functional tests for the {@link ITestDevice} file management APIs */ 50 @RunWith(DeviceJUnit4ClassRunner.class) 51 public class TestDeviceFileFuncTest implements IDeviceTest { 52 private String mInternalStorage; 53 private String mExternalStorage; 54 private TestDevice mTestDevice; 55 56 @Before setUp()57 public void setUp() throws Exception { 58 // Ensure at set-up that the device is available. 59 mTestDevice.waitForDeviceAvailable(); 60 mExternalStorage = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 61 mInternalStorage = "/data/local/tmp"; 62 } 63 64 /** Basic push-pull consistency test */ 65 @Test testPushPull_Basic()66 public void testPushPull_Basic() throws Exception { 67 String deviceFilePath = null; 68 File returnedFile = null; 69 try { 70 File testFile = WifiHelper.extractWifiUtilApk(); 71 deviceFilePath = String.format("%s/%s", mInternalStorage, "testPushPull_Basic.txt"); 72 mTestDevice.pushFile(testFile, deviceFilePath); 73 returnedFile = mTestDevice.pullFile(deviceFilePath); 74 75 assertNotNull(returnedFile); 76 77 assertEquals(computeChecksum(testFile), computeChecksum(returnedFile)); 78 } finally { 79 if (deviceFilePath != null) { 80 mTestDevice.deleteFile(deviceFilePath); 81 } 82 if (returnedFile != null) { 83 returnedFile.delete(); 84 } 85 } 86 } 87 /** Push-pull consistency test, but with external storage */ 88 @Test testPushPull_ExtStorage()89 public void testPushPull_ExtStorage() throws Exception { 90 String deviceFilePath = null; 91 File returnedFile = null; 92 try { 93 File testFile = WifiHelper.extractWifiUtilApk(); 94 deviceFilePath = 95 String.format("%s/%s", mExternalStorage, "tmp_testPushPull_ExtStorage.txt"); 96 mTestDevice.pushFile(testFile, deviceFilePath); 97 returnedFile = mTestDevice.pullFile(deviceFilePath); 98 99 assertNotNull(returnedFile); 100 101 assertEquals(computeChecksum(testFile), computeChecksum(returnedFile)); 102 } finally { 103 if (deviceFilePath != null) { 104 mTestDevice.deleteFile(deviceFilePath); 105 } 106 if (returnedFile != null) { 107 returnedFile.delete(); 108 } 109 } 110 } 111 /** Ensures the string method is also push-pull consistent */ 112 @Test testPushPull_FromString()113 public void testPushPull_FromString() throws Exception { 114 String deviceFilePath = null; 115 File returnedFile = null; 116 try { 117 String testString = generateRandomString(128 * 1024); // 128 kilobyte-ish string; 118 deviceFilePath = 119 String.format("%s/%s", mInternalStorage, "tmp_testPushPull_String.txt"); 120 mTestDevice.pushString(testString, deviceFilePath); 121 returnedFile = mTestDevice.pullFile(deviceFilePath); 122 123 assertNotNull(returnedFile); 124 125 assertEquals(computeChecksum(testString), computeChecksum(returnedFile)); 126 } finally { 127 if (deviceFilePath != null) { 128 mTestDevice.deleteFile(deviceFilePath); 129 } 130 if (returnedFile != null) { 131 returnedFile.delete(); 132 } 133 } 134 } 135 136 /** Ensures the pull contents method is also push-pull consistent */ 137 @Test testPushPull_PullContents()138 public void testPushPull_PullContents() throws Exception { 139 String deviceFilePath = null; 140 try { 141 String testString = generateRandomString(128 * 1024); // 128 kilobyte-ish string; 142 deviceFilePath = 143 String.format("%s/%s", mInternalStorage, "tmp_testPushPull_String.txt"); 144 mTestDevice.pushString(testString, deviceFilePath); 145 String returnedContents = mTestDevice.pullFileContents(deviceFilePath); 146 147 assertNotNull(returnedContents); 148 assertEquals(computeChecksum(testString), computeChecksum(returnedContents)); 149 } finally { 150 if (deviceFilePath != null) { 151 mTestDevice.deleteFile(deviceFilePath); 152 } 153 } 154 } 155 156 /** Tests when trying to pull a file that does not exist */ 157 @Test testPull_NoExist()158 public void testPull_NoExist() throws Exception { 159 String deviceFilePath = String.format("%s/%s", mInternalStorage, "thisfiledoesntexist"); 160 assertFalse( 161 String.format("%s exists", deviceFilePath), 162 mTestDevice.doesFileExist(deviceFilePath)); 163 assertNull(mTestDevice.pullFile(deviceFilePath)); 164 } 165 166 /** Tests when trying to pull a file that does not exist */ 167 @Test testPull_NoExistExtStorage()168 public void testPull_NoExistExtStorage() throws Exception { 169 String deviceFilePath = String.format("%s/%s", mExternalStorage, "thisfiledoesntexist"); 170 assertFalse( 171 String.format("%s exists", deviceFilePath), 172 mTestDevice.doesFileExist(deviceFilePath)); 173 assertNull(mTestDevice.pullFile(deviceFilePath)); 174 } 175 176 /** Tests when trying to pull a file that we lack permission to */ 177 @Test testPull_NoPermissions()178 public void testPull_NoPermissions() throws Exception { 179 String filePath = "/data/system/packages.xml"; 180 mTestDevice.disableAdbRoot(); 181 File returned = mTestDevice.pullFile(filePath); 182 assertNull(returned); 183 } 184 185 /** Tests pushing a non-existent file */ 186 @Test testPush_NoExist()187 public void testPush_NoExist() throws Exception { 188 final String filename = "this_file_does_not_exist.txt"; 189 String remotePath = null; 190 boolean needDelete = false; 191 try { 192 remotePath = String.format("%s/%s", mInternalStorage, filename); 193 boolean didPushFile = mTestDevice.pushFile(new File(filename), remotePath); 194 needDelete = didPushFile; 195 assertFalse(didPushFile); 196 } finally { 197 if (needDelete) { 198 mTestDevice.deleteFile(remotePath); 199 } 200 } 201 } 202 203 /** Tests push-pull consistency for directories */ 204 @Test testPushPullDir_Basic()205 public void testPushPullDir_Basic() throws Exception { 206 final int NUM_FILES = 10; // 10 files per level 207 final int FILE_SIZE = 16 * 1024; // 16 KB files 208 final String TEST_FILE_PREFIX = "test_file"; 209 final String TEST_LABEL = "testPushPullDir_Basic"; 210 File localParentDir = null; 211 String remotePath = null; 212 try { 213 // Set up directories for comparison 214 localParentDir = FileUtil.createTempDir(TEST_LABEL); 215 File localDir = FileUtil.createTempDir("local", localParentDir); 216 File pulledDir = FileUtil.createTempDir("pulled", localParentDir); 217 218 // populate the local one with some test files 219 for (int i = 0; i < NUM_FILES; i++) { 220 File tmp = 221 FileUtil.createTempFile( 222 String.format("%s_%s", TEST_FILE_PREFIX, i), "", localDir); 223 FileUtil.writeToFile(generateRandomString(FILE_SIZE), tmp); 224 } 225 226 remotePath = String.format("%s/%s", mInternalStorage, TEST_LABEL); 227 CommandResult res = 228 mTestDevice.executeShellV2Command(String.format("mkdir -p \"%s\"", remotePath)); 229 assertEquals(CommandStatus.SUCCESS, res.getStatus()); 230 231 boolean didPushSucceed = mTestDevice.pushDir(localDir, remotePath); 232 assertTrue(didPushSucceed); 233 234 boolean didPullSucceed = mTestDevice.pullDir(remotePath, pulledDir); 235 assertTrue(didPullSucceed); 236 237 // contains a bunch of equality assertions in the helper method 238 compareFiles(localDir, pulledDir); 239 } finally { 240 if (localParentDir != null && localParentDir.exists()) { 241 FileUtil.recursiveDelete(localParentDir); 242 } 243 if (remotePath != null) { 244 mTestDevice.deleteFile(remotePath); 245 } 246 } 247 } 248 249 /** Tests push-pull consistency for directories in external storage */ 250 @Test testPushPullDir_ExtStorage()251 public void testPushPullDir_ExtStorage() throws Exception { 252 final int NUM_FILES = 10; // 10 files per level 253 final int FILE_SIZE = 16 * 1024; // 16 KB files 254 final String TEST_FILE_PREFIX = "test_file"; 255 final String TEST_LABEL = "testPushPullDir_Basic"; 256 File localParentDir = null; 257 String remotePath = null; 258 try { 259 // Set up directories for comparison 260 localParentDir = FileUtil.createTempDir(TEST_LABEL); 261 File localDir = FileUtil.createTempDir("local", localParentDir); 262 File pulledDir = FileUtil.createTempDir("pulled", localParentDir); 263 264 // populate the local one with some test files 265 for (int i = 0; i < NUM_FILES; i++) { 266 File tmp = 267 FileUtil.createTempFile( 268 String.format("%s_%s", TEST_FILE_PREFIX, i), "", localDir); 269 FileUtil.writeToFile(generateRandomString(FILE_SIZE), tmp); 270 } 271 272 remotePath = String.format("%s/%s", mExternalStorage, TEST_LABEL); 273 CommandResult res = 274 mTestDevice.executeShellV2Command(String.format("mkdir -p \"%s\"", remotePath)); 275 assertEquals(CommandStatus.SUCCESS, res.getStatus()); 276 277 boolean didPushSucceed = mTestDevice.pushDir(localDir, remotePath); 278 assertTrue(didPushSucceed); 279 280 boolean didPullSucceed = mTestDevice.pullDir(remotePath, pulledDir); 281 assertTrue(didPullSucceed); 282 283 // contains a bunch of equality assertions in the helper method 284 compareFiles(localDir, pulledDir); 285 } finally { 286 if (localParentDir != null && localParentDir.exists()) { 287 FileUtil.recursiveDelete(localParentDir); 288 } 289 if (remotePath != null) { 290 mTestDevice.deleteFile(remotePath); 291 } 292 } 293 } 294 /** Tests the doesFileExist method with by pushing a file */ 295 @Test testDoesFileExist_Basic()296 public void testDoesFileExist_Basic() throws Exception { 297 String remotePath = null; 298 final String filename = "testDoesFileExist"; 299 try { 300 remotePath = String.format("%s/%s", mInternalStorage, filename); 301 302 mTestDevice.pushString(generateRandomString(10 * 1024), remotePath); 303 304 boolean doesFileExist = mTestDevice.doesFileExist(remotePath); 305 306 assertTrue(doesFileExist); 307 } finally { 308 if (remotePath != null) { 309 mTestDevice.deleteFile(remotePath); 310 } 311 } 312 } 313 /** Tests deleting a file, via push, delete, then doesFileExist */ 314 @Test testDeleteFile_Basic()315 public void testDeleteFile_Basic() throws Exception { 316 String remotePath = null; 317 final String testTag = "testDeleteFile_basic"; 318 final String filename = String.format("%s%s", testTag, ".txt"); 319 try { 320 remotePath = String.format("%s/%s", mInternalStorage, filename); 321 322 mTestDevice.pushString(generateRandomString(10 * 1024), remotePath); 323 324 assertTrue(mTestDevice.doesFileExist(remotePath)); 325 326 mTestDevice.deleteFile(remotePath); 327 328 assertFalse(mTestDevice.doesFileExist(remotePath)); 329 330 remotePath = null; 331 } finally { 332 if (remotePath != null) { 333 mTestDevice.deleteFile(remotePath); 334 } 335 } 336 } 337 /** Tests deleting a file on external storage by same way as the basic */ 338 @Test testDeleteFile_ExtStorage()339 public void testDeleteFile_ExtStorage() throws Exception { 340 String remotePath = null; 341 final String testTag = "testDeleteFile_extStorage"; 342 final String filename = String.format("%s%s", testTag, ".txt"); 343 try { 344 remotePath = String.format("%s/%s", mExternalStorage, filename); 345 346 mTestDevice.pushString(generateRandomString(10 * 1024), remotePath); 347 348 assertTrue(mTestDevice.doesFileExist(remotePath)); 349 350 mTestDevice.deleteFile(remotePath); 351 352 assertFalse(mTestDevice.doesFileExist(remotePath)); 353 354 remotePath = null; 355 } finally { 356 if (remotePath != null) { 357 mTestDevice.deleteFile(remotePath); 358 } 359 } 360 } 361 /** Tests isDirectory method by using a known existent directory */ 362 @Test testIsDirectory()363 public void testIsDirectory() throws Exception { 364 final int NUM_FILES = 10; // 10 files per level 365 final int FILE_SIZE = 16 * 1024; // 16 KB files 366 final String TEST_FILE_PREFIX = "test_file"; 367 final String TEST_LABEL = "testIsDirectory"; 368 File localParentDir = null; 369 String remotePath = null; 370 try { 371 // Set up directories for comparison 372 localParentDir = FileUtil.createTempDir(TEST_LABEL); 373 File localDir = FileUtil.createTempDir("local", localParentDir); 374 375 // populate the local one with some test files 376 for (int i = 0; i < NUM_FILES; i++) { 377 File tmp = 378 FileUtil.createTempFile( 379 String.format("%s_%s", TEST_FILE_PREFIX, i), "", localDir); 380 FileUtil.writeToFile(generateRandomString(FILE_SIZE), tmp); 381 } 382 383 remotePath = String.format("%s/%s", mInternalStorage, TEST_LABEL); 384 CommandResult res = 385 mTestDevice.executeShellV2Command(String.format("mkdir -p \"%s\"", remotePath)); 386 assertEquals(CommandStatus.SUCCESS, res.getStatus()); 387 388 boolean didPushSucceed = mTestDevice.pushDir(localDir, remotePath); 389 assertTrue(didPushSucceed); 390 391 assertTrue(mTestDevice.isDirectory(remotePath)); 392 } finally { 393 if (localParentDir != null && localParentDir.exists()) { 394 FileUtil.recursiveDelete(localParentDir); 395 } 396 if (remotePath != null) { 397 mTestDevice.deleteFile(remotePath); 398 } 399 } 400 } 401 /** Tests getChildren method by pushing a directory then listing its children */ 402 @Test testGetChildren_Basic()403 public void testGetChildren_Basic() throws Exception { 404 final int NUM_FILES = 10; // 10 files per level 405 final int FILE_SIZE = 16 * 1024; // 16 KB files 406 final String TEST_FILE_PREFIX = "test_file"; 407 final String TEST_LABEL = "testGetChildren_Basic"; 408 File localParentDir = null; 409 String remotePath = null; 410 try { 411 // Set up directories for comparison 412 localParentDir = FileUtil.createTempDir(TEST_LABEL); 413 File localDir = FileUtil.createTempDir("local", localParentDir); 414 415 // populate the local one with some test files 416 for (int i = 0; i < NUM_FILES; i++) { 417 File tmp = 418 FileUtil.createTempFile( 419 String.format("%s_%s", TEST_FILE_PREFIX, i), "", localDir); 420 FileUtil.writeToFile(generateRandomString(FILE_SIZE), tmp); 421 } 422 423 remotePath = String.format("%s/%s", mInternalStorage, TEST_LABEL); 424 CommandResult res = 425 mTestDevice.executeShellV2Command(String.format("mkdir -p \"%s\"", remotePath)); 426 assertEquals(CommandStatus.SUCCESS, res.getStatus()); 427 428 boolean didPushSucceed = mTestDevice.pushDir(localDir, remotePath); 429 assertTrue(didPushSucceed); 430 431 assertTrue(mTestDevice.isDirectory(remotePath)); 432 433 String[] pulledChildren = mTestDevice.getChildren(remotePath); 434 435 List<String> pulledChildrenList = 436 Arrays.asList(pulledChildren).stream().sorted().collect(Collectors.toList()); 437 List<String> localChildrenList = 438 Arrays.asList(localDir.list()).stream().sorted().collect(Collectors.toList()); 439 440 assertEquals(localChildrenList.size(), pulledChildrenList.size()); 441 442 for (int i = 0; i < localChildrenList.size(); i++) { 443 assertEquals(localChildrenList.get(i), pulledChildrenList.get(i)); 444 } 445 446 } finally { 447 if (localParentDir != null && localParentDir.exists()) { 448 FileUtil.recursiveDelete(localParentDir); 449 } 450 if (remotePath != null) { 451 mTestDevice.deleteFile(remotePath); 452 } 453 } 454 } 455 /** Tests getChildren on a file */ 456 @Test testGetChildren_NotDirectory()457 public void testGetChildren_NotDirectory() throws Exception { 458 final String TEST_LABEL = "testGetChildren_NotDirectory"; 459 String remotePath = null; 460 try { 461 remotePath = String.format("%s/%s", mInternalStorage, TEST_LABEL); 462 boolean didPushSucceed = 463 mTestDevice.pushString(generateRandomString(10 * 1024), remotePath); 464 assertTrue(didPushSucceed); 465 466 assertFalse(mTestDevice.isDirectory(remotePath)); 467 468 String[] pulledChildren = mTestDevice.getChildren(remotePath); 469 470 // This is really weird behavior, but is what the harness does right now. 471 // The contract wasn't specific on that method, so for now this test will at least 472 // enforce consistency 473 assertEquals(1, pulledChildren.length); 474 assertEquals(remotePath, pulledChildren[0]); 475 } finally { 476 if (remotePath != null) { 477 mTestDevice.deleteFile(remotePath); 478 } 479 } 480 } 481 /** Tests getChildren on an empty directory */ 482 @Test testGetChildren_EmptyDirectory()483 public void testGetChildren_EmptyDirectory() throws Exception { 484 final String TEST_LABEL = "testGetChildren_EmptyDir"; 485 String remotePath = null; 486 try { 487 remotePath = String.format("%s/%s", mInternalStorage, TEST_LABEL); 488 CommandResult res = 489 mTestDevice.executeShellV2Command(String.format("mkdir -p \"%s\"", remotePath)); 490 assertEquals(CommandStatus.SUCCESS, res.getStatus()); 491 492 assertTrue(mTestDevice.isDirectory(remotePath)); 493 494 String[] pulledChildren = mTestDevice.getChildren(remotePath); 495 assertEquals(0, pulledChildren.length); 496 } finally { 497 if (remotePath != null) { 498 mTestDevice.deleteFile(remotePath); 499 } 500 } 501 } 502 computeChecksum(File file)503 private static String computeChecksum(File file) throws IOException { 504 try (InputStream in = new FileInputStream(file)) { 505 return StreamUtil.calculateMd5(in); 506 } 507 } 508 computeChecksum(String str)509 private static String computeChecksum(String str) throws IOException { 510 try (InputStream in = new ByteArrayInputStream(str.getBytes("UTF-8"))) { 511 return StreamUtil.calculateMd5(in); 512 } 513 } 514 generateRandomString(int length)515 private static String generateRandomString(int length) { 516 int unicodeLower = 32; 517 int unicodeUpper = 126; 518 Random gen = new Random(); 519 520 return gen.ints(unicodeLower, unicodeUpper + 1) 521 .limit(length) 522 .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) 523 .toString(); 524 } 525 526 /** 527 * Performs a recursive pair-wise comparison of two file/directories 528 * 529 * <p>First this ensures the files have the same name (not path) and type (directory or file). 530 * Then this checks that the contents are the same. If they are directories then that means 531 * listing out their children, sorting them by name, ensuring the same number of elements, then 532 * recursing on them pairwise. If the two files are in fact regular files, to compare contents 533 * we just compute the checksum and compare those. 534 */ compareFiles(File firstFile, File secondFile)535 private static void compareFiles(File firstFile, File secondFile) throws Exception { 536 compareFiles(firstFile, secondFile, false); 537 return; 538 } 539 compareFiles(File firstFile, File secondFile, boolean checkName)540 private static void compareFiles(File firstFile, File secondFile, boolean checkName) 541 throws Exception { 542 assertEquals(firstFile.isDirectory(), secondFile.isDirectory()); 543 if (checkName) { 544 assertEquals(firstFile.getName(), secondFile.getName()); 545 } 546 547 if (firstFile.isDirectory()) { 548 List<Path> firstFileList; 549 try (Stream<Path> stream = Files.list(firstFile.toPath())) { 550 firstFileList = stream.sorted().collect(Collectors.toList()); 551 } 552 List<Path> secondFileList; 553 try (Stream<Path> stream = Files.list(secondFile.toPath())) { 554 secondFileList = stream.sorted().collect(Collectors.toList()); 555 } 556 assertEquals(firstFileList.size(), secondFileList.size()); 557 558 for (int i = 0; i < firstFileList.size(); i++) { 559 compareFiles(firstFileList.get(i).toFile(), secondFileList.get(i).toFile(), true); 560 } 561 } else { 562 assertEquals(computeChecksum(firstFile), computeChecksum(secondFile)); 563 } 564 return; 565 } 566 567 @Override setDevice(ITestDevice device)568 public void setDevice(ITestDevice device) { 569 mTestDevice = (TestDevice) device; 570 } 571 572 @Override getDevice()573 public ITestDevice getDevice() { 574 return mTestDevice; 575 } 576 } 577