1 /* 2 * Copyright 2022 Google LLC 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.google.android.libraries.mobiledatadownload.file.openers; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static org.junit.Assert.assertThrows; 20 import static org.mockito.Mockito.doThrow; 21 import static org.mockito.Mockito.spy; 22 23 import android.net.Uri; 24 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 25 import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend; 26 import com.google.android.libraries.mobiledatadownload.file.common.testing.FakeFileBackend; 27 import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri; 28 import com.google.android.libraries.mobiledatadownload.file.spi.Backend; 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import org.junit.Rule; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 import org.robolectric.RobolectricTestRunner; 37 import org.robolectric.annotation.Config; 38 39 @RunWith(RobolectricTestRunner.class) 40 public final class RecursiveDeleteOpenerTest { 41 42 @Rule public TemporaryUri tmpUri = new TemporaryUri(); 43 44 private final SynchronousFileStorage storage = 45 new SynchronousFileStorage(Arrays.asList(new JavaFileBackend())); 46 47 @Test open_nonExistentUri_throwsException()48 public void open_nonExistentUri_throwsException() throws IOException { 49 Uri dir = tmpUri.newDirectoryUri(); 50 Uri missing = Uri.withAppendedPath(dir, "a"); 51 assertThrows(IOException.class, () -> storage.open(missing, RecursiveDeleteOpener.create())); 52 } 53 54 @Test open_file_deletesFile()55 public void open_file_deletesFile() throws IOException { 56 Uri file = tmpUri.newUri(); 57 storage.open(file, WriteStringOpener.create("junk")); 58 59 storage.open(file, RecursiveDeleteOpener.create()); 60 61 assertThat(storage.exists(file)).isFalse(); 62 } 63 64 @Test open_emptyDirectory_deletesDirectory()65 public void open_emptyDirectory_deletesDirectory() throws IOException { 66 Uri dir = tmpUri.newDirectoryUri(); 67 68 storage.open(dir, RecursiveDeleteOpener.create()); 69 70 assertThat(storage.exists(dir)).isFalse(); 71 } 72 73 @Test open_nonEmptyDirectory_deletesChildrenThenDirectory()74 public void open_nonEmptyDirectory_deletesChildrenThenDirectory() throws IOException { 75 Uri dir = tmpUri.newDirectoryUri(); 76 77 // TODO: consider adding FileUri.fromFileUri to make this cleaner 78 Uri file0 = Uri.withAppendedPath(dir, "a"); 79 Uri file1 = Uri.withAppendedPath(dir, "b"); 80 storage.open(file0, WriteStringOpener.create("junk")); 81 storage.open(file1, WriteStringOpener.create("junk")); 82 83 storage.open(dir, RecursiveDeleteOpener.create()); 84 85 assertThat(storage.exists(file0)).isFalse(); 86 assertThat(storage.exists(file1)).isFalse(); 87 assertThat(storage.exists(dir)).isFalse(); 88 } 89 90 @Test open_directoryTree_recursesMultipleLevels()91 public void open_directoryTree_recursesMultipleLevels() throws IOException { 92 Uri dir = tmpUri.newDirectoryUri(); 93 94 Uri subDir = Uri.withAppendedPath(dir, "subDir"); 95 Uri subSubDir = Uri.withAppendedPath(subDir, "subSubDir"); 96 Uri emptySubDir = Uri.withAppendedPath(dir, "emptySubDir"); 97 storage.createDirectory(subDir); 98 storage.createDirectory(subSubDir); 99 storage.createDirectory(emptySubDir); 100 101 List<Uri> fileUris = new ArrayList<>(); 102 for (int i = 0; i != 5; i++) { 103 Uri uri = Uri.withAppendedPath(dir, Integer.toString(i)); 104 storage.open(uri, WriteStringOpener.create(Integer.toString(i))); 105 fileUris.add(uri); 106 } 107 108 storage.open(dir, RecursiveDeleteOpener.create()); 109 110 assertThat(storage.exists(dir)).isFalse(); 111 assertThat(storage.exists(subDir)).isFalse(); 112 assertThat(storage.exists(subSubDir)).isFalse(); 113 assertThat(storage.exists(emptySubDir)).isFalse(); 114 for (Uri uri : fileUris) { 115 assertThat(storage.exists(uri)).isFalse(); 116 } 117 } 118 119 @Test 120 @Config(sdk = 19) // addSuppressed is only available on SDK 19+ open_suppressesExceptionsUntilEnd()121 public void open_suppressesExceptionsUntilEnd() throws Exception { 122 Backend spyBackend = spy(new FakeFileBackend()); 123 SynchronousFileStorage storage = new SynchronousFileStorage(Arrays.asList(spyBackend)); 124 125 Uri dir = tmpUri.newDirectoryUri(); 126 127 Uri deletableFile0 = Uri.withAppendedPath(dir, "a"); 128 Uri undeletableFile = Uri.withAppendedPath(dir, "subDir/b"); 129 Uri deletableFile1 = Uri.withAppendedPath(dir, "subDir/subSubDir/c"); 130 storage.open(undeletableFile, WriteStringOpener.create("a")); 131 storage.open(deletableFile0, WriteStringOpener.create("b")); 132 storage.open(deletableFile1, WriteStringOpener.create("c")); 133 134 doThrow(IOException.class).when(spyBackend).deleteFile(undeletableFile); 135 136 IOException expected = 137 assertThrows(IOException.class, () -> storage.open(dir, RecursiveDeleteOpener.create())); 138 139 assertThat(expected.getSuppressed()).hasLength(3); // one for the file, two for the directories 140 141 assertThat(storage.exists(deletableFile0)).isFalse(); 142 assertThat(storage.exists(deletableFile1)).isFalse(); 143 assertThat(storage.exists(undeletableFile)).isTrue(); 144 assertThat(storage.exists(dir)).isTrue(); 145 } 146 } 147