blob: f9e2e12543c8ab46da1a44be71af07f2a3f58bdb [file] [log] [blame]
/*
* 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 WARRANTIES OR 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.beam.sdk.util;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.io.ByteSource;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.io.CharSource;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.io.Files;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for the {@link ZipFiles} class. These tests make sure that the handling of zip-files works
* fine.
*/
@RunWith(JUnit4.class)
public class ZipFilesTest {
@Rule public TemporaryFolder tmpFolder = new TemporaryFolder();
private File tmpDir;
@Rule public TemporaryFolder tmpOutputFolder = new TemporaryFolder();
private File zipFile;
@Before
public void setUp() throws Exception {
tmpDir = tmpFolder.getRoot();
zipFile = createZipFileHandle(); // the file is not actually created
}
/**
* Verify that zipping and unzipping works fine. We zip a directory having some subdirectories,
* unzip it again and verify the structure to be in place.
*/
@Test
public void testZipWithSubdirectories() throws Exception {
File zipDir = new File(tmpDir, "zip");
File subDir1 = new File(zipDir, "subDir1");
File subDir2 = new File(subDir1, "subdir2");
assertTrue(subDir2.mkdirs());
createFileWithContents(subDir2, "myTextFile.txt", "Simple Text");
assertZipAndUnzipOfDirectoryMatchesOriginal(tmpDir);
}
/** An empty subdirectory must have its own zip-entry. */
@Test
public void testEmptySubdirectoryHasZipEntry() throws Exception {
File zipDir = new File(tmpDir, "zip");
File subDirEmpty = new File(zipDir, "subDirEmpty");
assertTrue(subDirEmpty.mkdirs());
ZipFiles.zipDirectory(tmpDir, zipFile);
assertZipOnlyContains("zip/subDirEmpty/");
}
/** A directory with contents should not have a zip entry. */
@Test
public void testSubdirectoryWithContentsHasNoZipEntry() throws Exception {
File zipDir = new File(tmpDir, "zip");
File subDirContent = new File(zipDir, "subdirContent");
assertTrue(subDirContent.mkdirs());
createFileWithContents(subDirContent, "myTextFile.txt", "Simple Text");
ZipFiles.zipDirectory(tmpDir, zipFile);
assertZipOnlyContains("zip/subdirContent/myTextFile.txt");
}
@Test
public void testZipDirectoryToOutputStream() throws Exception {
createFileWithContents(tmpDir, "myTextFile.txt", "Simple Text");
File[] sourceFiles = tmpDir.listFiles();
Arrays.sort(sourceFiles);
assertThat(sourceFiles, not(arrayWithSize(0)));
try (FileOutputStream outputStream = new FileOutputStream(zipFile)) {
ZipFiles.zipDirectory(tmpDir, outputStream);
}
File outputDir = Files.createTempDir();
ZipFiles.unzipFile(zipFile, outputDir);
File[] outputFiles = outputDir.listFiles();
Arrays.sort(outputFiles);
assertThat(outputFiles, arrayWithSize(sourceFiles.length));
for (int i = 0; i < sourceFiles.length; i++) {
compareFileContents(sourceFiles[i], outputFiles[i]);
}
removeRecursive(outputDir.toPath());
assertTrue(zipFile.delete());
}
@Test
public void testEntries() throws Exception {
File zipDir = new File(tmpDir, "zip");
File subDir1 = new File(zipDir, "subDir1");
File subDir2 = new File(subDir1, "subdir2");
assertTrue(subDir2.mkdirs());
createFileWithContents(subDir2, "myTextFile.txt", "Simple Text");
ZipFiles.zipDirectory(tmpDir, zipFile);
try (ZipFile zip = new ZipFile(zipFile)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
for (ZipEntry entry : ZipFiles.entries(zip)) {
assertTrue(entries.hasMoreElements());
// ZipEntry doesn't override equals
assertEquals(entry.getName(), entries.nextElement().getName());
}
assertFalse(entries.hasMoreElements());
}
}
@Test
public void testAsByteSource() throws Exception {
File zipDir = new File(tmpDir, "zip");
assertTrue(zipDir.mkdirs());
createFileWithContents(zipDir, "myTextFile.txt", "Simple Text");
ZipFiles.zipDirectory(tmpDir, zipFile);
try (ZipFile zip = new ZipFile(zipFile)) {
ZipEntry entry = zip.getEntry("zip/myTextFile.txt");
ByteSource byteSource = ZipFiles.asByteSource(zip, entry);
if (entry.getSize() != -1) {
assertEquals(entry.getSize(), byteSource.size());
}
assertArrayEquals("Simple Text".getBytes(StandardCharsets.UTF_8), byteSource.read());
}
}
@Test
public void testAsCharSource() throws Exception {
File zipDir = new File(tmpDir, "zip");
assertTrue(zipDir.mkdirs());
createFileWithContents(zipDir, "myTextFile.txt", "Simple Text");
ZipFiles.zipDirectory(tmpDir, zipFile);
try (ZipFile zip = new ZipFile(zipFile)) {
ZipEntry entry = zip.getEntry("zip/myTextFile.txt");
CharSource charSource = ZipFiles.asCharSource(zip, entry, StandardCharsets.UTF_8);
assertEquals("Simple Text", charSource.read());
}
}
private void assertZipOnlyContains(String zipFileEntry) throws IOException {
try (ZipFile zippedFile = new ZipFile(zipFile)) {
assertEquals(1, zippedFile.size());
ZipEntry entry = zippedFile.entries().nextElement();
assertEquals(zipFileEntry, entry.getName());
}
}
/** try to unzip to a non-existent directory and make sure that it fails. */
@Test
public void testInvalidTargetDirectory() throws IOException {
File zipDir = new File(tmpDir, "zipdir");
assertTrue(zipDir.mkdir());
ZipFiles.zipDirectory(tmpDir, zipFile);
File invalidDirectory = new File("/foo/bar");
assertFalse(invalidDirectory.exists());
try {
ZipFiles.unzipFile(zipFile, invalidDirectory);
fail("We expect the IllegalArgumentException, but it never occured");
} catch (IllegalArgumentException e) {
// This is the expected exception - we passed the test.
}
}
/** Try to unzip to an existing directory, but failing to create directories. */
@Test
public void testDirectoryCreateFailed() throws IOException {
File zipDir = new File(tmpDir, "zipdir");
assertTrue(zipDir.mkdir());
ZipFiles.zipDirectory(tmpDir, zipFile);
File targetDirectory = Files.createTempDir();
// Touch a file where the directory should be.
Files.touch(new File(targetDirectory, "zipdir"));
try {
ZipFiles.unzipFile(zipFile, targetDirectory);
fail("We expect the IOException, but it never occured");
} catch (IOException e) {
// This is the expected exception - we passed the test.
}
}
/**
* zip and unzip a certain directory, and verify the content afterward to be identical.
*
* @param sourceDir the directory to zip
*/
private void assertZipAndUnzipOfDirectoryMatchesOriginal(File sourceDir) throws IOException {
File[] sourceFiles = sourceDir.listFiles();
Arrays.sort(sourceFiles);
File zipFile = createZipFileHandle();
ZipFiles.zipDirectory(sourceDir, zipFile);
File outputDir = Files.createTempDir();
ZipFiles.unzipFile(zipFile, outputDir);
File[] outputFiles = outputDir.listFiles();
Arrays.sort(outputFiles);
assertThat(outputFiles, arrayWithSize(sourceFiles.length));
for (int i = 0; i < sourceFiles.length; i++) {
compareFileContents(sourceFiles[i], outputFiles[i]);
}
removeRecursive(outputDir.toPath());
assertTrue(zipFile.delete());
}
/**
* Compare the content of two files or directories recursively.
*
* @param expected the expected directory or file content
* @param actual the actual directory or file content
*/
private void compareFileContents(File expected, File actual) throws IOException {
assertEquals(expected.isDirectory(), actual.isDirectory());
assertEquals(expected.getName(), actual.getName());
if (expected.isDirectory()) {
// Go through the children step by step.
File[] expectedChildren = expected.listFiles();
Arrays.sort(expectedChildren);
File[] actualChildren = actual.listFiles();
Arrays.sort(actualChildren);
assertThat(actualChildren, arrayWithSize(expectedChildren.length));
for (int i = 0; i < expectedChildren.length; i++) {
compareFileContents(expectedChildren[i], actualChildren[i]);
}
} else {
// Compare the file content itself.
assertTrue(Files.equal(expected, actual));
}
}
/** Create a File object to which we can safely zip a file. */
private File createZipFileHandle() throws IOException {
File zipFile = File.createTempFile("test", "zip", tmpOutputFolder.getRoot());
assertTrue(zipFile.delete());
return zipFile;
}
// This is not generally safe as it does not handle symlinks, etc. However it is safe
// enough for these tests.
private static void removeRecursive(Path path) throws IOException {
Iterable<File> files = Files.fileTraverser().depthFirstPostOrder(path.toFile());
for (File f : files) {
java.nio.file.Files.delete(f.toPath());
}
}
/** Create file dir/fileName with contents fileContents. */
private void createFileWithContents(File dir, String fileName, String fileContents)
throws IOException {
File txtFile = new File(dir, fileName);
Files.asCharSink(txtFile, StandardCharsets.UTF_8).write(fileContents);
}
}