blob: 0a7c478dafafc4ce18cc038505bce705a1550b75 [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.cassandra.io.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.assertj.core.api.Assertions;
import org.psjava.util.Triple;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_IO_TMPDIR;
public class FileTest
{
private static final java.io.File dir;
static
{
CassandraRelevantProperties.USE_NIX_RECURSIVE_DELETE.setBoolean(false);
java.io.File parent = new java.io.File(JAVA_IO_TMPDIR.getString()); //checkstyle: permit this instantiation
String dirName = Long.toHexString(ThreadLocalRandom.current().nextLong());
while (new java.io.File(parent, dirName).exists()) //checkstyle: permit this instantiation
dirName = Long.toHexString(ThreadLocalRandom.current().nextLong());
dir = new java.io.File(parent, dirName); //checkstyle: permit this instantiation
dir.mkdirs();
new File(dir).deleteRecursiveOnExit();
// PathUtils touches StorageService which touches StreamManager which requires configs be setup
DatabaseDescriptor.daemonInitialization();
}
@Test
public void testEquivalence() throws IOException
{
java.io.File notExists = new java.io.File(dir, "notExists"); //checkstyle: permit this instantiation
java.io.File regular = new java.io.File(dir, "regular"); //checkstyle: permit this instantiation
regular.createNewFile();
java.io.File regularLink = new java.io.File(dir, "regularLink"); //checkstyle: permit this instantiation
Files.createSymbolicLink(regularLink.toPath(), regular.toPath());
java.io.File emptySubdir = new java.io.File(dir, "empty"); //checkstyle: permit this instantiation
java.io.File emptySubdirLink = new java.io.File(dir, "emptyLink"); //checkstyle: permit this instantiation
emptySubdir.mkdir();
Files.createSymbolicLink(emptySubdirLink.toPath(), emptySubdir.toPath());
java.io.File nonEmptySubdir = new java.io.File(dir, "nonEmpty"); //checkstyle: permit this instantiation
java.io.File nonEmptySubdirLink = new java.io.File(dir, "nonEmptyLink"); //checkstyle: permit this instantiation
nonEmptySubdir.mkdir();
Files.createSymbolicLink(nonEmptySubdirLink.toPath(), nonEmptySubdir.toPath());
new java.io.File(nonEmptySubdir, "something").createNewFile(); //checkstyle: permit this instantiation
testEquivalence("");
List<Runnable> setup = ImmutableList.of(
() -> {},
() -> dir.setWritable(false),
() -> dir.setReadable(false),
() -> dir.setWritable(true)
);
for (Runnable run : setup)
{
run.run();
testEquivalence(notExists.getPath());
testEquivalence(nonAbsolute(notExists));
testEquivalence(regular.getPath());
testEquivalence(nonAbsolute(regular));
testEquivalence(regularLink.getPath());
testEquivalence(nonAbsolute(regularLink));
testEquivalence(emptySubdir.getPath());
testEquivalence(nonAbsolute(emptySubdir));
testEquivalence(emptySubdirLink.getPath());
testEquivalence(nonAbsolute(emptySubdirLink));
testEquivalence(nonEmptySubdir.getPath());
testEquivalence(nonAbsolute(nonEmptySubdir));
testEquivalence(nonEmptySubdirLink.getPath());
testEquivalence(nonAbsolute(nonEmptySubdirLink));
}
emptySubdirLink.delete();
regularLink.delete();
regular.delete();
emptySubdir.delete();
}
private static String nonAbsolute(java.io.File file)
{
return file.getParent() + File.pathSeparator() + ".." + File.pathSeparator() + file.getParentFile().getName() + File.pathSeparator() + file.getName();
}
private void testEquivalence(String path) throws IOException
{
java.io.File file = new java.io.File(path); //checkstyle: permit this instantiation
if (file.exists()) testExists(path);
else testNotExists(path);
}
private void testBasic(String path) throws IOException
{
// TODO: confirm - it seems that accuracy of lastModified may differ between APIs on Linux??
testEquivalence(path, f -> f.lastModified() / 1000, f -> f.lastModified() / 1000);
testEquivalence(path, java.io.File::length, File::length);
testEquivalence(path, java.io.File::canExecute, File::isExecutable);
testEquivalence(path, java.io.File::canRead, File::isReadable);
testEquivalence(path, java.io.File::canWrite, File::isWritable);
testEquivalence(path, java.io.File::exists, File::exists);
testEquivalence(path, java.io.File::isAbsolute, File::isAbsolute);
testEquivalence(path, java.io.File::isDirectory, File::isDirectory);
testEquivalence(path, java.io.File::isFile, File::isFile);
testEquivalence(path, java.io.File::getPath, File::path);
testEquivalence(path, java.io.File::getAbsolutePath, File::absolutePath);
testEquivalence(path, java.io.File::getCanonicalPath, File::canonicalPath);
testEquivalence(path, java.io.File::getParent, File::parentPath);
testEquivalence(path, java.io.File::toPath, File::toPath);
testEquivalence(path, java.io.File::list, File::tryListNames);
testEquivalence(path, java.io.File::listFiles, File::tryList);
java.io.File file = new java.io.File(path); //checkstyle: permit this instantiation
if (file.getParentFile() != null) testBasic(file.getParent());
if (!file.equals(file.getAbsoluteFile())) testBasic(file.getAbsolutePath());
if (!file.equals(file.getCanonicalFile())) testBasic(file.getCanonicalPath());
}
private void testPermissionsEquivalence(String path)
{
ImmutableList<Triple<BiFunction<java.io.File, Boolean, Boolean>, BiFunction<File, Boolean, Boolean>, Function<java.io.File, Boolean>>> tests = ImmutableList.of(
Triple.create(java.io.File::setReadable, File::trySetReadable, java.io.File::canRead),
Triple.create(java.io.File::setWritable, File::trySetWritable, java.io.File::canWrite),
Triple.create(java.io.File::setExecutable, File::trySetExecutable, java.io.File::canExecute)
);
for (Triple<BiFunction<java.io.File, Boolean, Boolean>, BiFunction<File, Boolean, Boolean>, Function<java.io.File, Boolean>> test : tests)
{
java.io.File file = new java.io.File(path); //checkstyle: permit this instantiation
boolean cur = test.v3.apply(file);
boolean canRead = file.canRead();
boolean canWrite = file.canWrite();
boolean canExecute = file.canExecute();
testEquivalence(path, f -> test.v1.apply(f, !cur), f -> test.v2.apply(f, !cur), (f, success) -> {
testEquivalence(path, java.io.File::canExecute, File::isExecutable);
testEquivalence(path, java.io.File::canRead, File::isReadable);
testEquivalence(path, java.io.File::canWrite, File::isWritable);
Assert.assertEquals(success != cur, test.v3.apply(file));
test.v1.apply(f, cur);
});
Assert.assertEquals(canRead, file.canRead());
Assert.assertEquals(canWrite, file.canWrite());
Assert.assertEquals(canExecute, file.canExecute());
}
}
private void testCreation(String path, IOConsumer<java.io.File> afterEach)
{
testEquivalence(path, java.io.File::createNewFile, File::createFileIfNotExists, afterEach);
testEquivalence(path, java.io.File::mkdir, File::tryCreateDirectory, afterEach);
testEquivalence(path, java.io.File::mkdirs, File::tryCreateDirectories, afterEach);
}
private void testExists(String path) throws IOException
{
testBasic(path);
testPermissionsEquivalence(path);
testCreation(path, ignore -> {});
testEquivalence(path, java.io.File::delete, File::tryDelete, (f, s) -> {if (s) f.createNewFile(); });
testTryVsConfirm(path, java.io.File::delete, File::delete, (f, s) -> {if (s) f.createNewFile(); });
}
private void testNotExists(String path) throws IOException
{
testBasic(path);
testPermissionsEquivalence(path);
testCreation(path, java.io.File::delete);
testEquivalence(path, java.io.File::delete, File::tryDelete);
testTryVsConfirm(path, java.io.File::delete, File::delete);
}
interface IOFn<I, O> { O apply(I in) throws IOException; }
interface IOConsumer<I1> { void accept(I1 i1) throws IOException; }
interface IOBiConsumer<I1, I2> { void accept(I1 i1, I2 i2) throws IOException; }
private <T> void testEquivalence(String path, IOFn<java.io.File, T> canonical, IOFn<File, T> test)
{
testEquivalence(path, canonical, test, ignore -> {});
}
private <T> void testEquivalence(String path, IOFn<java.io.File, T> canonical, IOFn<File, T> test, IOConsumer<java.io.File> afterEach)
{
testEquivalence(path, canonical, test, (f, ignore) -> afterEach.accept(f));
}
private <T> void testEquivalence(String path, IOFn<java.io.File, T> canonical, IOFn<File, T> test, IOBiConsumer<java.io.File, Boolean> afterEach)
{
java.io.File file = new java.io.File(path); //checkstyle: permit this instantiation
Object expect;
try
{
expect = canonical.apply(file);
}
catch (Throwable e)
{
expect = new Failed(e);
}
try { afterEach.accept(file, !(expect instanceof Failed) && !Boolean.FALSE.equals(expect)); } catch (IOException e) { throw new AssertionError(e); }
Object actual;
try
{
actual = test.apply(new File(path));
}
catch (Throwable e)
{
actual = new Failed(e);
}
try { afterEach.accept(file, !(actual instanceof Failed) && !Boolean.FALSE.equals(actual)); } catch (IOException e) { throw new AssertionError(e); }
if (expect instanceof String[] && actual instanceof String[]) Assert.assertArrayEquals((String[])expect, (String[])actual);
else if (expect instanceof java.io.File[] && actual instanceof File[]) assertArrayEquals((java.io.File[]) expect, (File[]) actual);
else Assert.assertEquals(path + "," + canonical.toString(), expect, actual);
}
private void testTryVsConfirm(String path, Predicate<java.io.File> canonical, IOConsumer<File> test)
{
testTryVsConfirm(path, canonical, test, (f, s) -> {});
}
private void testTryVsConfirm(String path, Predicate<java.io.File> canonical, IOConsumer<File> test, IOConsumer<java.io.File> afterEach)
{
testTryVsConfirm(path, canonical, test, (f, ignore) -> afterEach.accept(f));
}
private void testTryVsConfirm(String path, Predicate<java.io.File> canonical, IOConsumer<File> test, IOBiConsumer<java.io.File, Boolean> afterEach)
{
java.io.File file = new java.io.File(path); //checkstyle: permit this instantiation
boolean expect = canonical.test(file);
try { afterEach.accept(file, expect); } catch (IOException e) { throw new AssertionError(e); }
boolean actual;
try
{
test.accept(new File(path));
actual = true;
}
catch (Throwable e)
{
actual = false;
}
try { afterEach.accept(file, actual); } catch (IOException e) { throw new AssertionError(e); }
Assert.assertEquals(path + "," + canonical.toString(), expect, actual);
}
private static void assertArrayEquals(java.io.File[] expect, File[] actual)
{
Assert.assertEquals(expect.length, actual.length);
for (int i = 0 ; i < expect.length ; ++i)
Assert.assertEquals(expect[i].getPath(), actual[i].path());
}
private static class Failed
{
final Throwable with;
private Failed(Throwable with)
{
this.with = with;
}
@Override
public boolean equals(Object obj)
{
return obj instanceof Failed;
}
@Override
public String toString()
{
StringWriter sw = new StringWriter();
with.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
@Test
public void testDeletes() throws IOException
{
File subdir = new File(dir, "deletes");
File file = new File(dir, "f");
subdir.tryCreateDirectory();
Assert.assertTrue(new File(subdir, "subsubdir").tryCreateDirectory());
subdir.deleteRecursive();
Assert.assertFalse(subdir.exists());
subdir.tryCreateDirectory();
file.createFileIfNotExists();
Assert.assertTrue(new File(subdir, "subsubdir").tryCreateDirectory());
long start = System.nanoTime();
RateLimiter rateLimiter = RateLimiter.create(2);
subdir.deleteRecursive(rateLimiter);
file.delete(rateLimiter);
long end = System.nanoTime();
Assert.assertTrue("" + NANOSECONDS.toMillis(end - start), SECONDS.toNanos(1) <= end - start);
Assert.assertFalse(subdir.exists());
Assert.assertFalse(file.exists());
}
@Test
public void testAncestry()
{
Assert.assertTrue(new File("somewhere/../").isAncestorOf(new File("somewhere")));
Assert.assertTrue(new File("../").isAncestorOf(new File("")));
}
@Test
public void testOverwrite() throws Exception
{
File f = new File(dir, UUID.randomUUID().toString());
// write
ByteBuffer buf = ByteBuffer.wrap(RandomUtils.nextBytes(100));
try (FileChannel fc = f.newWriteChannel(File.WriteMode.OVERWRITE))
{
fc.write(buf);
}
Assertions.assertThat(f.length()).isEqualTo(buf.array().length);
Assertions.assertThat(Files.readAllBytes(f.toPath())).isEqualTo(buf.array());
// overwrite
buf = ByteBuffer.wrap(RandomUtils.nextBytes(50));
try (FileChannel fc = f.newWriteChannel(File.WriteMode.OVERWRITE))
{
fc.write(buf);
}
Assertions.assertThat(f.length()).isEqualTo(buf.array().length);
Assertions.assertThat(Files.readAllBytes(f.toPath())).isEqualTo(buf.array());
}
@Test
public void testAppend() throws Exception
{
File f = new File(dir, UUID.randomUUID().toString());
// write
ByteBuffer buf1 = ByteBuffer.wrap(RandomUtils.nextBytes(100));
try (FileChannel fc = f.newWriteChannel(File.WriteMode.APPEND))
{
fc.write(buf1);
}
Assertions.assertThat(f.length()).isEqualTo(buf1.array().length);
Assertions.assertThat(Files.readAllBytes(f.toPath())).isEqualTo(buf1.array());
// overwrite
ByteBuffer buf2 = ByteBuffer.wrap(RandomUtils.nextBytes(50));
try (FileChannel fc = f.newWriteChannel(File.WriteMode.APPEND))
{
fc.write(buf2);
}
Assertions.assertThat(f.length()).isEqualTo(buf1.array().length + buf2.array().length);
ByteBuffer buf = ByteBuffer.allocate(buf1.array().length + buf2.array().length);
buf.put(buf1.array()).put(buf2.array());
Assertions.assertThat(Files.readAllBytes(f.toPath())).isEqualTo(buf.array());
}
}