• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common.testing;
17 
18 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.appendFile;
19 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.createFile;
20 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.makeArrayOfBytesContent;
21 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.readFileInBytes;
22 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.readFileInBytesFromSource;
23 import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.writeFileToSink;
24 import static com.google.common.truth.Truth.assertThat;
25 import static org.junit.Assert.assertThrows;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.net.Uri;
29 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
30 import com.google.android.libraries.mobiledatadownload.file.common.FileConvertible;
31 import com.google.android.libraries.mobiledatadownload.file.common.MalformedUriException;
32 import com.google.android.libraries.mobiledatadownload.file.openers.AppendStreamOpener;
33 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener;
34 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
35 import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.collect.Lists;
38 import com.google.common.primitives.Bytes;
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileNotFoundException;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.util.Arrays;
47 import java.util.List;
48 import org.junit.Before;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.rules.TestName;
52 
53 /**
54  * Base class for {@code Backend} tests that exercises common behavior expected of all
55  * implementations. Concrete test cases must specify a test runner, extend from this class, and
56  * implement the abstract setup methods. Subclasses are free to add additional test methods in order
57  * to exercise backend-specific behavior using the provided {@link #storage}.
58  *
59  * <p>If the backend under test does not support a specific feature, the test subclass should
60  * override the appropriate {@code supportsX()} and return false in order to skip the associated
61  * unit tests. NOTE: this is adopted from Guava and seems like the least-bad strategy.
62  *
63  * <p>Abstract setup methods may be called before the {@code @Before} methods of the subclass, and
64  * so should not depend on them. {@code @BeforeClass}, lazy initialization, and static
65  * initialization are viable alternatives.
66  */
67 public abstract class BackendTestBase {
68 
69   private SynchronousFileStorage storage;
70   private static final byte[] TEST_CONTENT = makeArrayOfBytesContent();
71   private static final byte[] OTHER_CONTENT = makeArrayOfBytesContent(6);
72 
73   @Rule public TestName testName = new TestName();
74 
75   /** Returns the concrete {@code Backend} instance to be tested. */
backend()76   protected abstract Backend backend();
77 
78   /** Enables unit tests verifying {@link Backend#openForAppend}. */
supportsAppend()79   protected boolean supportsAppend() {
80     return true;
81   }
82 
83   /** Enables unit tests verifying {@link Backend#rename}. */
supportsRename()84   protected boolean supportsRename() {
85     return true;
86   }
87 
88   /**
89    * Enables unit tests verifying {@link Backend#createDirectory}, {@link Backend#isDirectory},
90    * {@link Backend#deleteDirectory}, {@link Backend#children}, and writing to a subdirectory uri.
91    */
supportsDirectories()92   protected boolean supportsDirectories() {
93     return true;
94   }
95 
96   /** Enables unit tests verifying that {@link FileConvertible} can be returned directly. */
supportsFileConvertible()97   protected boolean supportsFileConvertible() {
98     return true;
99   }
100 
101   /** Enable unit tests verifying {@link Backend#toFile}. */
supportsToFile()102   protected boolean supportsToFile() {
103     return true;
104   }
105 
106   /**
107    * Returns a URI to a temporary directory for writing test data to. The {@code Backend} should be
108    * able to {@code openForWrite} a file to this directory without any additional setup code.
109    */
legalUriBase()110   protected abstract Uri legalUriBase() throws IOException;
111 
112   /**
113    * Returns a list of URIs for which {@code Backend.openForRead(uri)} is expected to throw {@code
114    * MalformedUriException} without any additional setup code.
115    */
illegalUrisToRead()116   protected List<Uri> illegalUrisToRead() {
117     return ImmutableList.of();
118   }
119 
120   /**
121    * Returns a list of URIs for which {@code Backend.openForWrite(uri)} is expected to throw {@code
122    * MalformedUriException} without any additional setup code.
123    */
illegalUrisToWrite()124   protected List<Uri> illegalUrisToWrite() {
125     return ImmutableList.of();
126   }
127 
128   /**
129    * Returns a list of URIs for which {@code Backend.openForAppend(uri)} is expected to throw {@code
130    * MalformedUriException} without any additional setup code.
131    */
illegalUrisToAppend()132   protected List<Uri> illegalUrisToAppend() {
133     return ImmutableList.of();
134   }
135 
136   @Before
setUpStorage()137   public final void setUpStorage() {
138     assertThat(backend()).isNotNull();
139     storage = new SynchronousFileStorage(ImmutableList.of(backend()), ImmutableList.of());
140   }
141 
142   /** Returns the storage instance used in testing. */
storage()143   protected SynchronousFileStorage storage() {
144     return storage;
145   }
146 
147   @Test
name_returnsNonEmptyString()148   public void name_returnsNonEmptyString() {
149     assertThat(backend().name()).isNotEmpty();
150   }
151 
152   @Test
openForRead_withMissingFile_throwsFileNotFound()153   public void openForRead_withMissingFile_throwsFileNotFound() throws Exception {
154     Uri uri = uriForTestMethod();
155     assertThrows(FileNotFoundException.class, () -> storage.open(uri, ReadStreamOpener.create()));
156   }
157 
158   @Test
openForRead_withIllegalUri_throwsIllegalArgumentException()159   public void openForRead_withIllegalUri_throwsIllegalArgumentException() throws Exception {
160     for (Uri uri : illegalUrisToRead()) {
161       assertThrows(MalformedUriException.class, () -> storage.open(uri, ReadStreamOpener.create()));
162     }
163   }
164 
165   @Test
openForRead_readsWrittenContent()166   public void openForRead_readsWrittenContent() throws Exception {
167     Uri uri = uriForTestMethod();
168     createFile(storage, uri, TEST_CONTENT);
169     assertThat(readFileInBytes(storage, uri)).isEqualTo(TEST_CONTENT);
170   }
171 
172   @Test
openForRead_returnsFileConvertible()173   public void openForRead_returnsFileConvertible() throws Exception {
174     assumeTrue(supportsFileConvertible());
175 
176     Uri uri = uriForTestMethod();
177     createFile(storage(), uri, TEST_CONTENT);
178 
179     InputStream stream = backend().openForRead(uri);
180     assertThat(stream).isInstanceOf(FileConvertible.class);
181     File file = ((FileConvertible) stream).toFile();
182     assertThat(readFileInBytesFromSource(new FileInputStream(file))).isEqualTo(TEST_CONTENT);
183   }
184 
185   @Test
openForWrite_withIllegalUri_throwsIllegalArgumentException()186   public void openForWrite_withIllegalUri_throwsIllegalArgumentException() throws Exception {
187     for (Uri uri : illegalUrisToWrite()) {
188       assertThrows(
189           MalformedUriException.class, () -> storage.open(uri, WriteStreamOpener.create()));
190     }
191   }
192 
193   @Test
openForWrite_withFailedDirectoryCreation_throwsException()194   public void openForWrite_withFailedDirectoryCreation_throwsException() throws Exception {
195     assumeTrue(supportsDirectories());
196 
197     Uri parent = uriForTestMethod();
198     createFile(storage, parent, TEST_CONTENT);
199 
200     Uri child = parent.buildUpon().appendPath("child").build();
201     assertThrows(IOException.class, () -> storage.open(child, WriteStreamOpener.create()));
202   }
203 
204   @Test
openForWrite_overwritesExistingContent()205   public void openForWrite_overwritesExistingContent() throws Exception {
206     Uri uri = uriForTestMethod();
207     createFile(storage, uri, OTHER_CONTENT);
208 
209     createFile(storage, uri, TEST_CONTENT);
210     assertThat(readFileInBytes(storage, uri)).isEqualTo(TEST_CONTENT);
211   }
212 
213   @Test
openForWrite_createsParentDirectory()214   public void openForWrite_createsParentDirectory() throws Exception {
215     assumeTrue(supportsDirectories());
216 
217     Uri parent = uriForTestMethod();
218     Uri child = parent.buildUpon().appendPath("child").build();
219 
220     createFile(storage, child, TEST_CONTENT);
221     assertThat(storage.isDirectory(parent)).isTrue();
222     assertThat(storage.exists(child)).isTrue();
223   }
224 
225   @Test
openForWrite_returnsFileConvertible()226   public void openForWrite_returnsFileConvertible() throws Exception {
227     assumeTrue(supportsFileConvertible());
228 
229     Uri uri = uriForTestMethod();
230     try (OutputStream stream = backend().openForWrite(uri)) {
231       assertThat(stream).isInstanceOf(FileConvertible.class);
232       File file = ((FileConvertible) stream).toFile();
233       writeFileToSink(new FileOutputStream(file), TEST_CONTENT);
234     }
235     assertThat(readFileInBytes(storage(), uri)).isEqualTo(TEST_CONTENT);
236   }
237 
238   @Test
openForAppend_withIllegalUri_throwsIllegalArgumentException()239   public void openForAppend_withIllegalUri_throwsIllegalArgumentException() throws Exception {
240     assumeTrue(supportsAppend());
241 
242     for (Uri uri : illegalUrisToAppend()) {
243       assertThrows(
244           MalformedUriException.class, () -> storage.open(uri, AppendStreamOpener.create()));
245     }
246   }
247 
248   @Test
openForAppend_appendsContent()249   public void openForAppend_appendsContent() throws Exception {
250     assumeTrue(supportsAppend());
251 
252     Uri uri = uriForTestMethod();
253     createFile(storage, uri, OTHER_CONTENT);
254 
255     appendFile(storage, uri, TEST_CONTENT);
256     assertThat(readFileInBytes(storage, uri)).isEqualTo(Bytes.concat(OTHER_CONTENT, TEST_CONTENT));
257   }
258 
259   @Test
openForAppend_returnsFileConvertible()260   public void openForAppend_returnsFileConvertible() throws Exception {
261     assumeTrue(supportsAppend());
262     assumeTrue(supportsFileConvertible());
263 
264     Uri uri = uriForTestMethod();
265     createFile(storage, uri, OTHER_CONTENT);
266     try (OutputStream stream = backend().openForAppend(uri)) {
267       assertThat(stream).isInstanceOf(FileConvertible.class);
268       File file = ((FileConvertible) stream).toFile();
269       writeFileToSink(new FileOutputStream(file, /* append= */ true), TEST_CONTENT);
270     }
271     assertThat(readFileInBytes(storage(), uri))
272         .isEqualTo(Bytes.concat(OTHER_CONTENT, TEST_CONTENT));
273   }
274 
275   @Test
deleteFile_deletesFile()276   public void deleteFile_deletesFile() throws Exception {
277     Uri uri = uriForTestMethod();
278     createFile(storage, uri, TEST_CONTENT);
279 
280     storage.deleteFile(uri);
281     assertThat(storage.exists(uri)).isFalse();
282   }
283 
284   @Test
deleteFile_onDirectory_throwsFileNotFound()285   public void deleteFile_onDirectory_throwsFileNotFound() throws Exception {
286     assumeTrue(supportsDirectories());
287 
288     Uri uri = uriForTestMethod();
289     storage.createDirectory(uri);
290 
291     assertThrows(FileNotFoundException.class, () -> storage.deleteFile(uri));
292   }
293 
294   @Test
deleteFile_onMissingFile_throwsFileNotFound()295   public void deleteFile_onMissingFile_throwsFileNotFound() throws Exception {
296     Uri uri = uriForTestMethod();
297     assertThrows(FileNotFoundException.class, () -> storage.deleteFile(uri));
298   }
299 
300   @Test
rename_renamesFile()301   public void rename_renamesFile() throws Exception {
302     assumeTrue(supportsRename());
303 
304     Uri uri1 = uriForTestMethodWithSuffix("1");
305     Uri uri2 = uriForTestMethodWithSuffix("2");
306     Uri uri3 = uriForTestMethodWithSuffix("3");
307     createFile(storage, uri1, OTHER_CONTENT);
308     createFile(storage, uri2, TEST_CONTENT);
309 
310     storage.rename(uri2, uri3);
311     storage.rename(uri1, uri2);
312     assertThat(storage.exists(uri1)).isFalse();
313     assertThat(readFileInBytes(storage, uri2)).isEqualTo(OTHER_CONTENT);
314     assertThat(readFileInBytes(storage, uri3)).isEqualTo(TEST_CONTENT);
315   }
316 
317   @Test
rename_renamesDirectory()318   public void rename_renamesDirectory() throws Exception {
319     assumeTrue(supportsRename());
320     assumeTrue(supportsDirectories());
321 
322     Uri uriA = uriForTestMethodWithSuffix("a");
323     Uri uriB = uriForTestMethodWithSuffix("b");
324 
325     storage.createDirectory(uriA);
326     storage.rename(uriA, uriB);
327     assertThat(storage.isDirectory(uriA)).isFalse();
328     assertThat(storage.isDirectory(uriB)).isTrue();
329   }
330 
331   @Test
exists_returnsTrueIfFileExists()332   public void exists_returnsTrueIfFileExists() throws Exception {
333     Uri uri = uriForTestMethod();
334     assertThat(storage.exists(uri)).isFalse();
335 
336     createFile(storage, uri, TEST_CONTENT);
337     assertThat(storage.exists(uri)).isTrue();
338   }
339 
340   @Test
exists_returnsTrueIfDirectoryExists()341   public void exists_returnsTrueIfDirectoryExists() throws Exception {
342     assumeTrue(supportsDirectories());
343 
344     Uri uri = uriForTestMethod();
345     assertThat(storage.exists(uri)).isFalse();
346 
347     storage.createDirectory(uri);
348     assertThat(storage.exists(uri)).isTrue();
349   }
350 
351   @Test
isDirectory_returnsTrueIfDirectoryExists()352   public void isDirectory_returnsTrueIfDirectoryExists() throws Exception {
353     assumeTrue(supportsDirectories());
354 
355     Uri uri = uriForTestMethod();
356     assertThat(storage.isDirectory(uri)).isFalse();
357 
358     storage.createDirectory(uri);
359     assertThat(storage.isDirectory(uri)).isTrue();
360   }
361 
362   @Test
isDirectory_returnsFalseIfDoesntExist()363   public void isDirectory_returnsFalseIfDoesntExist() throws Exception {
364     assumeTrue(supportsDirectories());
365 
366     Uri uri = uriForTestMethod();
367     assertThat(storage.isDirectory(uri)).isFalse();
368   }
369 
370   @Test
isDirectory_returnsFalseIfIsFile()371   public void isDirectory_returnsFalseIfIsFile() throws Exception {
372     assumeTrue(supportsDirectories());
373 
374     Uri uri = uriForTestMethod();
375     createFile(storage, uri, TEST_CONTENT);
376     assertThat(storage.isDirectory(uri)).isFalse();
377   }
378 
379   @Test
createDirectory_createsDirectory()380   public void createDirectory_createsDirectory() throws Exception {
381     assumeTrue(supportsDirectories());
382 
383     Uri uri = uriForTestMethod();
384     storage.createDirectory(uri);
385     assertThat(storage.isDirectory(uri)).isTrue();
386   }
387 
388   @Test
createDirectory_createsParentDirectory()389   public void createDirectory_createsParentDirectory() throws Exception {
390     assumeTrue(supportsDirectories());
391 
392     Uri parent = uriForTestMethod();
393     Uri child = parent.buildUpon().appendPath("child").build();
394 
395     storage.createDirectory(child);
396     assertThat(storage.isDirectory(child)).isTrue();
397     assertThat(storage.isDirectory(parent)).isTrue();
398   }
399 
400   @Test
fileSize_withMissingFile_returnsZero()401   public void fileSize_withMissingFile_returnsZero() throws Exception {
402     Uri uri = uriForTestMethod();
403     assertThat(storage.fileSize(uri)).isEqualTo(0);
404   }
405 
406   @Test
fileSize_returnsSizeOfFile()407   public void fileSize_returnsSizeOfFile() throws Exception {
408     Uri uri = uriForTestMethod();
409 
410     backend().openForWrite(uri).close();
411     assertThat(storage.fileSize(uri)).isEqualTo(0);
412 
413     createFile(storage, uri, TEST_CONTENT);
414     assertThat(storage.fileSize(uri)).isEqualTo(TEST_CONTENT.length);
415   }
416 
417   @Test
fileSize_withDirReturns0()418   public void fileSize_withDirReturns0() throws Exception {
419     assumeTrue(supportsDirectories());
420 
421     Uri uri = uriForTestMethod();
422     storage.createDirectory(uri);
423     assertThat(storage.fileSize(uri)).isEqualTo(0);
424   }
425 
426   @Test
deleteDirectory_shouldDeleteEmptyDirectory()427   public void deleteDirectory_shouldDeleteEmptyDirectory() throws Exception {
428     assumeTrue(supportsDirectories());
429 
430     Uri uri = uriForTestMethod();
431     assertThat(storage.isDirectory(uri)).isFalse();
432     storage.createDirectory(uri);
433     assertThat(storage.isDirectory(uri)).isTrue();
434     storage.deleteDirectory(uri);
435     assertThat(storage.isDirectory(uri)).isFalse();
436   }
437 
438   @Test
deleteDirectory_shouldNOTDeleteNonEmptyDirectory()439   public void deleteDirectory_shouldNOTDeleteNonEmptyDirectory() throws Exception {
440     assumeTrue(supportsDirectories());
441 
442     Uri uri = uriForTestMethod();
443     storage.createDirectory(uri);
444     Uri fileUri = uri.buildUpon().appendPath("file").build();
445     createFile(storage, fileUri, TEST_CONTENT);
446 
447     assertThat(storage.isDirectory(uri)).isTrue();
448     assertThrows(IOException.class, () -> storage.deleteDirectory(uri));
449     assertThat(storage.isDirectory(uri)).isTrue();
450 
451     storage.deleteFile(fileUri);
452     storage.deleteDirectory(uri);
453     assertThat(storage.isDirectory(uri)).isFalse();
454   }
455 
456   @Test
deleteDirectory_onFileShouldThrow()457   public void deleteDirectory_onFileShouldThrow() throws Exception {
458     assumeTrue(supportsDirectories());
459 
460     Uri uri = uriForTestMethod();
461     createFile(storage, uri, TEST_CONTENT);
462 
463     assertThrows(IOException.class, () -> storage.deleteDirectory(uri));
464   }
465 
466   @Test
children_withEmptyDirectoryShouldReturnEmpty()467   public void children_withEmptyDirectoryShouldReturnEmpty() throws Exception {
468     assumeTrue(supportsDirectories());
469 
470     Uri uri = uriForTestMethod();
471     storage.createDirectory(uri);
472 
473     assertThat(storage.children(uri)).isEmpty();
474   }
475 
476   @Test
children_onNotFoundShouldThrow()477   public void children_onNotFoundShouldThrow() throws Exception {
478     assumeTrue(supportsDirectories());
479 
480     Uri uri = uriForTestMethod();
481 
482     assertThrows(IOException.class, () -> storage.children(uri));
483   }
484 
485   @Test
children_onFileShouldThrow()486   public void children_onFileShouldThrow() throws Exception {
487     assumeTrue(supportsDirectories());
488 
489     Uri uri = uriForTestMethod();
490     createFile(storage, uri, TEST_CONTENT);
491 
492     assertThrows(IOException.class, () -> storage.children(uri));
493   }
494 
495   @Test
children_shouldReturnFilesAndSubDirectories()496   public void children_shouldReturnFilesAndSubDirectories() throws Exception {
497     assumeTrue(supportsDirectories());
498 
499     Uri uri = uriForTestMethod();
500     storage.createDirectory(uri);
501 
502     List<Uri> fileUris =
503         Arrays.asList(
504             uri.buildUpon().appendPath("file1").build(),
505             uri.buildUpon().appendPath("file2").build(),
506             uri.buildUpon().appendPath("file3").build());
507     for (Uri file : fileUris) {
508       createFile(storage, file, TEST_CONTENT);
509     }
510 
511     List<Uri> subdirUris =
512         Arrays.asList(
513             uri.buildUpon().appendPath("dir1").build(),
514             uri.buildUpon().appendPath("dir2").build(),
515             uri.buildUpon().appendPath("dir3").build());
516     for (Uri subdir : subdirUris) {
517       storage.createDirectory(subdir);
518     }
519     List<Uri> subdirUrisWithTrailingSlashes =
520         Lists.transform(subdirUris, u -> Uri.parse(u.toString() + "/"));
521 
522     List<Uri> expected = Lists.newArrayList();
523     expected.addAll(fileUris);
524     expected.addAll(subdirUrisWithTrailingSlashes);
525     assertThat(storage.children(uri)).containsExactlyElementsIn(expected);
526   }
527 
528   @Test
toFile_converts()529   public void toFile_converts() throws Exception {
530     assumeTrue(supportsToFile());
531 
532     Uri uri = uriForTestMethod();
533     createFile(storage, uri, TEST_CONTENT);
534     File file = backend().toFile(uri);
535     assertThat(readFileInBytesFromSource(new FileInputStream(file))).isEqualTo(TEST_CONTENT);
536   }
537 
538   /** Returns a URI in the test directory unique to the current test method. */
uriForTestMethod()539   protected Uri uriForTestMethod() throws IOException {
540     return legalUriBase().buildUpon().appendPath(testName.getMethodName()).build();
541   }
542 
543   /** Returns a URI in the test directory unique to the current test method and {@code suffix}. */
uriForTestMethodWithSuffix(String suffix)544   private Uri uriForTestMethodWithSuffix(String suffix) throws IOException {
545     return legalUriBase().buildUpon().appendPath(testName.getMethodName() + "-" + suffix).build();
546   }
547 }
548