1/*
2 * Copyright (C) 2015 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 */
16package com.android.timezone.distro;
17
18import junit.framework.TestCase;
19
20import android.system.Os;
21import android.system.OsConstants;
22import android.system.StructStat;
23
24import java.io.File;
25import java.io.FileNotFoundException;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.nio.charset.StandardCharsets;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collections;
32import java.util.List;
33import libcore.io.IoUtils;
34import libcore.io.Libcore;
35
36/**
37 * Tests for {@link FileUtils}.
38 */
39public class FileUtilsTest extends TestCase {
40
41    private List<File> testFiles = new ArrayList<>();
42
43    @Override
44    public void tearDown() throws Exception {
45        // Delete in reverse order
46        Collections.reverse(testFiles);
47        for (File tempFile : testFiles) {
48            tempFile.delete();
49        }
50        super.tearDown();
51    }
52
53    public void testDeleteRecursive() throws Exception {
54        File dir = createTempDir();
55        File file1 = createRegularFile(dir, "file1");
56        File file2 = createRegularFile(dir, "file2");
57        File symLink1 = createSymlink(file1, dir, "symLink1");
58        File subDir = createDir(dir, "subDir");
59        File file3 = createRegularFile(subDir, "subFile1");
60        File file4 = createRegularFile(subDir, "subFile2");
61        File symLink2 = createSymlink(file1, dir, "symLink2");
62
63        File otherDir = createTempDir();
64        File otherFile = createRegularFile(otherDir, "kept");
65
66        File linkToOtherDir = createSymlink(otherDir, subDir, "linkToOtherDir");
67        File linkToOtherFile = createSymlink(otherFile, subDir, "linkToOtherFile");
68
69        File[] filesToDelete = { dir, file1, file2, symLink1, subDir, file3, file4, symLink2,
70                linkToOtherDir, linkToOtherFile };
71        File[] filesToKeep = { otherDir, otherFile };
72        assertFilesExist(filesToDelete);
73        assertFilesExist(filesToKeep);
74
75        FileUtils.deleteRecursive(dir);
76        assertFilesDoNotExist(filesToDelete);
77        assertFilesExist(filesToKeep);
78    }
79
80    public void testIsSymlink() throws Exception {
81        File dir = createTempDir();
82        File subDir = createDir(dir, "subDir");
83        File fileInSubDir = createRegularFile(subDir, "fileInSubDir");
84        File normalFile = createRegularFile(dir, "normalFile");
85        File symlinkToDir = createSymlink(subDir, dir, "symlinkToDir");
86        File symlinkToFile = createSymlink(fileInSubDir, dir, "symlinkToFile");
87        File symlinkToFileInSubDir = createSymlink(fileInSubDir, dir, "symlinkToFileInSubDir");
88        File normalFileViaSymlink = new File(symlinkToDir, "normalFile");
89
90        assertFalse(FileUtils.isSymlink(dir));
91        assertFalse(FileUtils.isSymlink(subDir));
92        assertFalse(FileUtils.isSymlink(fileInSubDir));
93        assertFalse(FileUtils.isSymlink(normalFile));
94        assertTrue(FileUtils.isSymlink(symlinkToDir));
95        assertTrue(FileUtils.isSymlink(symlinkToFile));
96        assertTrue(FileUtils.isSymlink(symlinkToFileInSubDir));
97        assertFalse(FileUtils.isSymlink(normalFileViaSymlink));
98    }
99
100    public void testCreateSubFile() throws Exception {
101        File dir1 = createTempDir().getCanonicalFile();
102
103        File actualSubFile = FileUtils.createSubFile(dir1, "file");
104        assertEquals(new File(dir1, "file"), actualSubFile);
105
106        File existingSubFile = createRegularFile(dir1, "file");
107        actualSubFile = FileUtils.createSubFile(dir1, "file");
108        assertEquals(existingSubFile, actualSubFile);
109
110        File existingSubDir = createDir(dir1, "subdir");
111        actualSubFile = FileUtils.createSubFile(dir1, "subdir");
112        assertEquals(existingSubDir, actualSubFile);
113
114        assertCreateSubFileThrows(dir1, "../file");
115        assertCreateSubFileThrows(dir1, "../../file");
116        assertCreateSubFileThrows(dir1, "../otherdir/file");
117
118        File dir2 = createTempDir().getCanonicalFile();
119        createSymlink(dir2, dir1, "symlinkToDir2");
120        assertCreateSubFileThrows(dir1, "symlinkToDir2");
121
122        assertCreateSubFileThrows(dir1, "symlinkToDir2/fileInSymlinkedDir");
123
124        createRegularFile(dir1, "symlinkToDir2/fileInSymlinkedDir");
125        assertCreateSubFileThrows(dir1, "symlinkToDir2/fileInSymlinkedDir");
126    }
127
128    public void testEnsureDirectoryExists() throws Exception {
129        File dir = createTempDir();
130
131        File exists = new File(dir, "exists");
132        assertTrue(exists.mkdir());
133        assertTrue(exists.setReadable(true /* readable */, true /* ownerOnly */));
134        assertTrue(exists.setExecutable(true /* readable */, true /* ownerOnly */));
135        FileUtils.ensureDirectoriesExist(exists, true /* makeWorldReadable */);
136        assertDirExistsAndIsAccessible(exists, false /* requireWorldReadable */);
137
138        File subDir = new File(dir, "subDir");
139        assertFalse(subDir.exists());
140        FileUtils.ensureDirectoriesExist(subDir, true /* makeWorldReadable */);
141        assertDirExistsAndIsAccessible(subDir, true /* requireWorldReadable */);
142
143        File one = new File(dir, "one");
144        File two = new File(one, "two");
145        File three = new File(two, "three");
146        FileUtils.ensureDirectoriesExist(three, true /* makeWorldReadable */);
147        assertDirExistsAndIsAccessible(one, true /* requireWorldReadable */);
148        assertDirExistsAndIsAccessible(two, true /* requireWorldReadable */);
149        assertDirExistsAndIsAccessible(three, true /* requireWorldReadable */);
150    }
151
152    public void testEnsureDirectoriesExist_noPermissions() throws Exception {
153        File dir = createTempDir();
154        assertDirExistsAndIsAccessible(dir, false /* requireWorldReadable */);
155
156        File unreadableSubDir = new File(dir, "unreadableSubDir");
157        assertTrue(unreadableSubDir.mkdir());
158        assertTrue(unreadableSubDir.setReadable(false /* readable */, true /* ownerOnly */));
159        assertTrue(unreadableSubDir.setExecutable(false /* readable */, true /* ownerOnly */));
160
161        File toCreate = new File(unreadableSubDir, "toCreate");
162        try {
163            FileUtils.ensureDirectoriesExist(toCreate, true /* makeWorldReadable */);
164            fail();
165        } catch (IOException expected) {
166        }
167        assertDirExistsAndIsAccessible(dir, false /* requireWorldReadable */);
168        assertFalse(unreadableSubDir.canRead() && unreadableSubDir.canExecute());
169        assertFalse(toCreate.exists());
170    }
171
172    public void testEnsureFileDoesNotExist() throws Exception {
173        File dir = createTempDir();
174
175        FileUtils.ensureFileDoesNotExist(new File(dir, "doesNotExist"));
176
177        File exists1 = createRegularFile(dir, "exists1");
178        assertTrue(exists1.exists());
179        FileUtils.ensureFileDoesNotExist(exists1);
180        assertFalse(exists1.exists());
181
182        exists1 = createRegularFile(dir, "exists1");
183        File symlink = createSymlink(exists1, dir, "symlinkToFile");
184        assertTrue(symlink.exists());
185        FileUtils.ensureFileDoesNotExist(symlink);
186        assertFalse(symlink.exists());
187        assertTrue(exists1.exists());
188
189        // Only files and symlinks supported. We do not delete directories.
190        File emptyDir = createTempDir();
191        try {
192            FileUtils.ensureFileDoesNotExist(emptyDir);
193            fail();
194        } catch (IOException expected) {
195        }
196        assertTrue(emptyDir.exists());
197    }
198
199    // This test does not pass when run as root because root can do anything even if the permissions
200    // don't allow it.
201    public void testEnsureFileDoesNotExist_noPermission() throws Exception {
202        File dir = createTempDir();
203
204        File protectedDir = createDir(dir, "protected");
205        File undeletable = createRegularFile(protectedDir, "undeletable");
206        assertTrue(protectedDir.setWritable(false));
207        assertTrue(undeletable.exists());
208        try {
209            FileUtils.ensureFileDoesNotExist(undeletable);
210            fail();
211        } catch (IOException expected) {
212        } finally {
213            assertTrue(protectedDir.setWritable(true)); // Reset for clean-up
214        }
215        assertTrue(undeletable.exists());
216    }
217
218    public void testCheckFilesExist() throws Exception {
219        File dir = createTempDir();
220        createRegularFile(dir, "exists1");
221        File subDir = createDir(dir, "subDir");
222        createRegularFile(subDir, "exists2");
223        assertTrue(FileUtils.filesExist(dir, "exists1", "subDir/exists2"));
224        assertFalse(FileUtils.filesExist(dir, "doesNotExist"));
225        assertFalse(FileUtils.filesExist(dir, "subDir/doesNotExist"));
226    }
227
228    public void testReadBytes() throws Exception {
229        File dir = createTempDir();
230
231        try {
232            FileUtils.readBytes(new File(dir, "doesNotExist"), 0);
233            fail();
234        } catch (IllegalArgumentException expected) {
235        }
236
237        try {
238            FileUtils.readBytes(new File(dir, "doesNotExist"), 10);
239            fail();
240        } catch (FileNotFoundException expected) {
241        }
242
243        byte[] contents = "One\nTwo\nThree\n".getBytes(StandardCharsets.US_ASCII);
244        File file = createFile(contents);
245        byte[] oneByte = FileUtils.readBytes(file, 1);
246        assertEquals(1, oneByte.length);
247        assertEquals(contents[0], oneByte[0]);
248
249        byte[] allBytes = FileUtils.readBytes(file, contents.length);
250        assertTrue(Arrays.equals(contents, allBytes));
251
252        byte[] exhaustedFileRead = FileUtils.readBytes(file, contents.length + 1);
253        assertTrue(Arrays.equals(contents, exhaustedFileRead));
254    }
255
256    public void testCreateEmptyFile() throws Exception {
257        File dir = createTempDir();
258        File file = new File(dir, "one");
259        assertFalse(file.exists());
260        FileUtils.createEmptyFile(file);
261        assertTrue(file.exists());
262        assertEquals(0, file.length());
263    }
264
265    public void testCreateEmptyFile_isDir() throws Exception {
266        File dir = createTempDir();
267        assertTrue(dir.exists());
268        assertTrue(dir.isDirectory());
269
270        try {
271            FileUtils.createEmptyFile(dir);
272        } catch (FileNotFoundException expected) {
273        }
274        assertTrue(dir.exists());
275        assertTrue(dir.isDirectory());
276    }
277
278    public void testCreateEmptyFile_truncatesExisting() throws Exception {
279        File dir = createTempDir();
280        File file = new File(dir, "one");
281
282        try (FileOutputStream fos = new FileOutputStream(file)) {
283            fos.write(new byte[1000]);
284        }
285        assertTrue(file.exists());
286        assertEquals(1000, file.length());
287
288        FileUtils.createEmptyFile(file);
289        assertTrue(file.exists());
290        assertEquals(0, file.length());
291    }
292
293    private File createFile(byte[] contents) throws IOException {
294        File file = File.createTempFile(getClass().getSimpleName(), ".txt");
295        try (FileOutputStream fos = new FileOutputStream(file)) {
296            fos.write(contents);
297        }
298        return file;
299    }
300
301    private File createSymlink(File file, File symlinkDir, String symlinkName) throws Exception {
302        assertTrue(file.exists());
303
304        File symlink = new File(symlinkDir, symlinkName);
305        Os.symlink(file.getAbsolutePath(), symlink.getAbsolutePath());
306        testFiles.add(symlink);
307        return symlink;
308    }
309
310    private static void assertCreateSubFileThrows(File parentDir, String name) {
311        try {
312            FileUtils.createSubFile(parentDir, name);
313            fail();
314        } catch (IOException expected) {
315            assertTrue(expected.getMessage().contains("must exist beneath"));
316        }
317    }
318
319    private static void assertFilesDoNotExist(File... files) {
320        for (File f : files) {
321            assertFalse(f + " unexpectedly exists", f.exists());
322        }
323    }
324
325    private static void assertFilesExist(File... files) {
326        for (File f : files) {
327            assertTrue(f + " expected to exist", f.exists());
328        }
329    }
330
331    private static void assertDirExistsAndIsAccessible(File dir, boolean requireWorldReadable)
332            throws Exception {
333        assertTrue(dir.exists() && dir.isDirectory() && dir.canRead() && dir.canExecute());
334
335        String path = dir.getCanonicalPath();
336        StructStat sb = Libcore.os.stat(path);
337        int mask = OsConstants.S_IXUSR | OsConstants.S_IRUSR;
338        if (requireWorldReadable) {
339            mask = mask | OsConstants.S_IXGRP | OsConstants.S_IRGRP
340                    | OsConstants.S_IXOTH | OsConstants.S_IROTH;
341        }
342        assertTrue("Permission mask required: " + Integer.toOctalString(mask),
343                (sb.st_mode & mask) == mask);
344    }
345
346    private File createTempDir() {
347        final String tempPrefix = getClass().getSimpleName();
348        File tempDir = IoUtils.createTemporaryDirectory(tempPrefix);
349        testFiles.add(tempDir);
350        return tempDir;
351    }
352
353    private File createDir(File parentDir, String name) {
354        File dir = new File(parentDir, name);
355        assertTrue(dir.mkdir());
356        testFiles.add(dir);
357        return dir;
358    }
359
360    private File createRegularFile(File dir, String name) throws Exception {
361        File file = new File(dir, name);
362        try (FileOutputStream fos = new FileOutputStream(file)) {
363            fos.write("Hello".getBytes());
364        }
365        testFiles.add(file);
366        return file;
367    }
368}
369