/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIESOR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.aries.util.filesystem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.apache.aries.unittest.junit.Assert; import org.apache.aries.util.IORuntimeException; import org.apache.aries.util.io.IOUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; /** * This class contains tests for the virtual file system. */ public class FileSystemTest { /** * Make sure we correctly understand the content of the application when the * application is an exploded directory. This test just checks that the * root directory returns the expected information. * * @throws IOException */ @Test(expected=UnsupportedOperationException.class) public void basicRootDirTestsWithFiles() throws IOException { File baseDir = new File(getTestResourceDir(), "/app1"); File manifest = new File(baseDir, "META-INF/APPLICATION.MF"); IDirectory dir = FileSystem.getFSRoot(baseDir); runBasicRootDirTests(dir, baseDir.length(), manifest.lastModified()); } /** * Make sure we correctly understand the directory structure for exploded * directories. * * @throws IOException */ @Test public void basicDirTestsWithFiles() throws IOException { File baseDir = new File(getTestResourceDir(), "/app1"); IDirectory dir = FileSystem.getFSRoot(baseDir); File desiredFile = new File(baseDir, "META-INF/APPLICATION.MF"); runBasicDirTest(dir, desiredFile.length(), desiredFile.lastModified()); runBasicDirTest(dir.toCloseable(), desiredFile.length(), desiredFile.lastModified()); } /** * Make sure we correctly understand the content of the application when the * application is a zip. This test just checks that the * root directory returns the expected information. * * @throws IOException */ @Test(expected=UnsupportedOperationException.class) public void basicRootDirTestsWithZip() throws IOException { File baseDir = new File("fileSystemTest/app2.zip"); IDirectory dir = FileSystem.getFSRoot(baseDir); runBasicRootDirTests(dir, baseDir.length(), baseDir.lastModified()); } /** * Make sure we correctly understand the content of the application when the * application is a zip. This test just checks that the * root directory returns the expected information. * * @throws IOException */ @Test(expected=UnsupportedOperationException.class) public void basicRootDirTestsWithZipInputStream() throws IOException { File baseDir = new File("fileSystemTest/app2.zip"); ICloseableDirectory dir = FileSystem.getFSRoot(new FileInputStream(baseDir)); try { runBasicRootDirTests(dir, baseDir.length(), baseDir.lastModified()); } finally { dir.close(); } } @Test public void testInvalidFSRoot() throws IOException { File baseDir = new File(getTestResourceDir(), "/app1"); File manifest = new File(baseDir, "META-INF/APPLICATION.MF"); try { IDirectory dir = FileSystem.getFSRoot(manifest); fail("Should have thrown an IORuntimeException"); } catch (IORuntimeException e) { // good! } } /** * Make sure that operations work on zip files nested in file IDirectories * @throws IOException */ @Test public void nestedZipInDirectory() throws IOException { IDirectory dir = FileSystem.getFSRoot(new File("").getAbsoluteFile()); // base convert does not do nested zips IDirectory zip = dir.getFile("fileSystemTest/app2.zip").convert(); assertNull(zip); // basic conversion works zip = dir.getFile("fileSystemTest/app2.zip").convertNested(); assertNotNull(zip); // we get the parent and our path right assertNotNull(zip.getParent()); assertEquals("fileSystemTest", zip.getParent().getName()); assertEquals("fileSystemTest/app2.zip", zip.getName()); // files inside the nested zip have the correct path as well IFile appMf = zip.getFile("META-INF/APPLICATION.MF"); assertNotNull(appMf); assertEquals("fileSystemTest/app2.zip/META-INF/APPLICATION.MF", appMf.getName()); checkManifest(appMf.open()); // root is right assertFalse(zip.isRoot()); assertEquals(dir, zip.getRoot()); assertEquals(dir, appMf.getRoot()); // check URLs are correct checkManifest(appMf.toURL().openStream()); runBasicDirTest(zip, "fileSystemTest/app2.zip/", appMf.getSize(), appMf.getLastModified()); } /** * Make sure that the operations work with zip files inside other zip files. Performance is not going to be great though :) */ @Test public void nestedZipInZip() throws IOException { IDirectory outer = FileSystem.getFSRoot(new File("fileSystemTest/outer.zip")); IFile innerFile = outer.getFile("app2.zip"); assertNotNull(innerFile); IDirectory inner = innerFile.convertNested(); assertNotNull(inner); File desiredFile = new File(new File(getTestResourceDir(), "/app1"), "META-INF/APPLICATION.MF"); // no size information when stream reading :( runBasicDirTest(inner, "app2.zip/", -1, desiredFile.lastModified()); runBasicDirTest(inner.toCloseable(), "app2.zip/", desiredFile.length(), desiredFile.lastModified()); } /** * Make sure that the operations work with zip files inside other zip files. Performance is not going to be great though :) */ @Test public void nestedZipInZipInputStream() throws Exception { ICloseableDirectory outer = FileSystem.getFSRoot(new FileInputStream("fileSystemTest/outer.zip")); try { IFile innerFile = outer.getFile("app2.zip"); assertNotNull(innerFile); IDirectory inner = innerFile.convertNested(); assertNotNull(inner); File desiredFile = new File(new File(getTestResourceDir(), "/app1"), "META-INF/APPLICATION.MF"); // no size information when stream reading :( runBasicDirTest(inner, "app2.zip/", -1, desiredFile.lastModified()); runBasicDirTest(inner.toCloseable(), "app2.zip/", desiredFile.length(), desiredFile.lastModified()); } finally { outer.close(); Field f = outer.getClass().getDeclaredField("tempFile"); f.setAccessible(true); assertFalse(((File)f.get(outer)).exists()); } } /** * Make sure we correctly understand the directory structure for zips. * * @throws IOException */ @Test public void basicDirTestsWithZip() throws IOException { File baseDir = new File("fileSystemTest/app2.zip"); IDirectory dir = FileSystem.getFSRoot(baseDir); assertTrue(dir.toString(), dir.toString().endsWith("app2.zip")); File desiredFile = new File(new File(getTestResourceDir(), "/app1"), "META-INF/APPLICATION.MF"); runBasicDirTest(dir, desiredFile.length(), desiredFile.lastModified()); runBasicDirTest(dir.toCloseable(), desiredFile.length(), desiredFile.lastModified()); } /** * Make sure we correctly understand the directory structure for zips. * * @throws IOException */ @Test public void basicDirTestsWithZipInputStream() throws IOException { File baseDir = new File("fileSystemTest/app2.zip"); ICloseableDirectory dir = FileSystem.getFSRoot(new FileInputStream(baseDir)); try { File desiredFile = new File(new File(getTestResourceDir(), "/app1"), "META-INF/APPLICATION.MF"); runBasicDirTest(dir, desiredFile.length(), desiredFile.lastModified()); runBasicDirTest(dir.toCloseable(), desiredFile.length(), desiredFile.lastModified()); } finally { dir.close(); } } @Test public void zipCloseableZipSimplePerformanceTest() throws IOException { int N = 100000; File baseDir = new File("fileSystemTest/app2.zip"); ZipFile zip = new ZipFile(baseDir); long start = System.currentTimeMillis(); for (int i=0; i<N; i++) { ZipEntry ze = zip.getEntry("META-INF/APPLICATION.MF"); InputStream is = zip.getInputStream(ze); is.close(); } long duration = System.currentTimeMillis() - start; // normal zip files ICloseableDirectory dir = FileSystem.getFSRoot(baseDir).toCloseable(); start = System.currentTimeMillis(); for (int i=0; i<N; i++) { IFile appMf = dir.getFile("META-INF/APPLICATION.MF"); InputStream is = appMf.open(); is.close(); } long duration2 = System.currentTimeMillis() - start; dir.close(); // within an order of magnitude assertTrue("ZipFile: "+duration+", IDirectory: "+duration2 , duration2 < 10*duration ); // nested zip files IDirectory outer = FileSystem.getFSRoot(new File("fileSystemTest/outer.zip")); IFile innerFile = outer.getFile("app2.zip"); dir = innerFile.convertNested().toCloseable(); start = System.currentTimeMillis(); for (int i=0; i<N; i++) { IFile appMf = dir.getFile("META-INF/APPLICATION.MF"); InputStream is = appMf.open(); is.close(); } long duration3 = System.currentTimeMillis() - start; dir.close(); // within an order of magnitude assertTrue("ZipFile: "+duration+", IDirectory: "+duration3 , duration3 < 10*duration ); } /** * Zip up the app1 directory to create a zippped version before running any * tests. * * @throws IOException */ @BeforeClass public static void makeZip() throws IOException { File zipFile = new File("fileSystemTest/app2.zip"); zipFile.getParentFile().mkdirs(); ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); int index = new File(getTestResourceDir(), "/app1").getAbsolutePath().length(); writeEnties(out, new File(getTestResourceDir(), "/app1"), index); out.close(); zipFile = new File("outer.zip"); out = new ZipOutputStream(new FileOutputStream(zipFile)); index = new File("fileSystemTest").getAbsolutePath().length(); writeEnties(out, new File("fileSystemTest"), index); out.close(); if (!!!zipFile.renameTo(new File("fileSystemTest/outer.zip"))) throw new IOException("Rename failed"); } private static File getTestResourceDir() { File root = new File("").getAbsoluteFile(); if (root.getName().equals("target")) return new File("../src/test/resources"); else return new File("src/test/resources"); } /** * Make sure the test zip is deleted afterwards. */ @AfterClass public static void destroyZip() { IOUtils.deleteRecursive(new File("fileSystemTest")); } /** * This method writes the given directory into the provided zip output stream. * It removes the first <code>index</code> bytes from the absolute path name * when building the zip. * * @param zos the zip output stream to use * @param f the directory to write into the zip. * @param index how much of the file name to chop off. * @throws IOException */ public static void writeEnties(ZipOutputStream zos, File f, int index) throws IOException { File[] files = f.listFiles(); if (files != null) { for (File file : files) { String fileName = file.getAbsolutePath().substring(index + 1); // Bug 1954: replace any '\' characters with '/' - required by ZipEntry fileName = fileName.replace('\\', '/'); if (file.isDirectory()) fileName = fileName + "/"; ZipEntry ze = new ZipEntry(fileName); ze.setSize(file.length()); ze.setTime(file.lastModified()); zos.putNextEntry(ze); if (file.isFile()) { InputStream is = new FileInputStream(file); byte[] buffer = new byte[(int)file.length()]; int len = is.read(buffer); zos.write(buffer, 0, len); is.close(); // Bug 1594 } zos.closeEntry(); if (file.isDirectory()) { writeEnties(zos, file, index); } } } } /** * This method makes sure that the data is correctly understood from disk. It * is called for both the file and zip versions of the test to ensure we have * consistent results. * * @param dir The IDirectory for the root of the vFS. * @param len The size of the file. * @param time The time the file was last updated. * @throws IOException */ public void runBasicRootDirTests(IDirectory dir, long len, long time) throws IOException { assertEquals("The root file system name is not correct", "", dir.getName()); assertEquals("The size of the file is not correct", len, dir.getSize()); // This assertion just isn't working on Hudson as of build #79 // assertEquals("The last modified time of the file is not correct", time, dir.getLastModified()); assertNull("I managed to get a parent of a root", dir.getParent()); assertTrue("The root dir does not know it is a dir", dir.isDirectory()); assertFalse("The root dir has an identity crisis and thinks it is a file", dir.isFile()); dir.open(); } private void runBasicDirTest(IDirectory dir, long len, long time) throws IOException { runBasicDirTest(dir, "", len, time); } /** * This method makes sure that the data is correctly understood from disk. It * is called for both the file and zip versions of the test to ensure we have * consistent results. * * @param dir The IDirectory for the root of the vFS. * @param len The size of the file. * @param time The time the file was last updated. * @throws IOException */ private void runBasicDirTest(IDirectory dir, String namePrefix, long len, long time) throws IOException { assertNull("for some reason our fake app has a fake blueprint file.", dir.getFile("OSGI-INF/blueprint/aries.xml")); IFile file = dir.getFile("META-INF/APPLICATION.MF"); assertNotNull("we could not find the application manifest", file); assertNull(file.convert()); assertNull(file.convertNested()); assertEquals(namePrefix+"META-INF/APPLICATION.MF", file.getName().replace('\\', '/')); assertTrue("The last update time is not within 2 seconds of the expected value. Expected: " + time + " Actual: " + file.getLastModified(), Math.abs(time - file.getLastModified()) < 2000); assertEquals(len, file.getSize()); assertEquals(namePrefix+"META-INF", file.getParent().getName()); assertFalse(file.isDirectory()); assertTrue(file.isFile()); List<IFile> files = dir.listFiles(); filterOutSvn(files); assertEquals(1, files.size()); List<IFile> allFiles = dir.listAllFiles(); filterOutSvn(allFiles); assertEquals(3, allFiles.size()); assertEquals(namePrefix+"META-INF", allFiles.get(1).getParent().getName()); IFile metaInf = files.get(0); assertTrue(metaInf.isDirectory()); assertEquals(namePrefix+"META-INF", metaInf.getName()); assertNotNull(metaInf.convert()); files = metaInf.convert().listAllFiles(); filterOutSvn(files); assertEquals(2, files.size()); for (IFile aFile : dir) { if (!aFile.getName().contains(".svn")) { assertTrue(aFile.isDirectory()); assertEquals(namePrefix+"META-INF", aFile.getName()); assertNotNull(aFile.convert()); } } checkManifest(file.open()); IFile applicationMF2 = dir.getFile("META-INF/APPLICATION.MF"); Assert.assertEqualsContract(file, applicationMF2, dir); Assert.assertHashCodeEquals(file, applicationMF2, true); } private void filterOutSvn(Collection<IFile> files) { Iterator<IFile> its = files.iterator(); while (its.hasNext()) { IFile f = its.next(); if (f.getName().toLowerCase().contains(".svn")) its.remove(); } } private void checkManifest(InputStream is) throws IOException { Manifest man = new Manifest(is); //remember to close the input stream after use is.close(); assertEquals("com.travel.reservation", man.getMainAttributes().getValue("Application-SymbolicName")); } }