blob: 4867554854022e1df12625a5dd98b61bf0320828 [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.lucene.util;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.mockfile.DisableFsyncFS;
import org.apache.lucene.mockfile.ExtrasFS;
import org.apache.lucene.mockfile.HandleLimitFS;
import org.apache.lucene.mockfile.LeakFS;
import org.apache.lucene.mockfile.ShuffleFS;
import org.apache.lucene.mockfile.VerboseFS;
import org.apache.lucene.mockfile.WindowsFS;
import org.apache.lucene.util.LuceneTestCase.SuppressFileSystems;
import org.apache.lucene.util.LuceneTestCase.SuppressFsync;
import org.apache.lucene.util.LuceneTestCase.SuppressTempFileChecks;
import com.carrotsearch.randomizedtesting.RandomizedContext;
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
/**
* Checks and cleans up temporary files.
*
* @see LuceneTestCase#createTempDir()
* @see LuceneTestCase#createTempFile()
*/
final class TestRuleTemporaryFilesCleanup extends TestRuleAdapter {
/**
* Retry to create temporary file name this many times.
*/
private static final int TEMP_NAME_RETRY_THRESHOLD = 9999;
/**
* Writeable temporary base folder.
*/
private Path javaTempDir;
/**
* Per-test class temporary folder.
*/
private Path tempDirBase;
/**
* Per-test filesystem
*/
private FileSystem fileSystem;
/**
* Suite failure marker.
*/
private final TestRuleMarkFailure failureMarker;
/**
* A queue of temporary resources to be removed after the
* suite completes.
* @see #registerToRemoveAfterSuite(Path)
*/
private final static List<Path> cleanupQueue = new ArrayList<Path>();
public TestRuleTemporaryFilesCleanup(TestRuleMarkFailure failureMarker) {
this.failureMarker = failureMarker;
}
/**
* Register temporary folder for removal after the suite completes.
*/
void registerToRemoveAfterSuite(Path f) {
assert f != null;
if (LuceneTestCase.LEAVE_TEMPORARY) {
System.err.println("INFO: Will leave temporary file: " + f.toAbsolutePath());
return;
}
synchronized (cleanupQueue) {
cleanupQueue.add(f);
}
}
@Override
protected void before() throws Throwable {
super.before();
assert tempDirBase == null;
fileSystem = initializeFileSystem();
javaTempDir = initializeJavaTempDir();
}
// os/config-independent limit for too many open files
// TODO: can we make this lower?
private static final int MAX_OPEN_FILES = 2048;
private boolean allowed(Set<String> avoid, Class<? extends FileSystemProvider> clazz) {
if (avoid.contains("*") || avoid.contains(clazz.getSimpleName())) {
return false;
} else {
return true;
}
}
private FileSystem initializeFileSystem() {
Class<?> targetClass = RandomizedContext.current().getTargetClass();
Set<String> avoid = new HashSet<>();
if (targetClass.isAnnotationPresent(SuppressFileSystems.class)) {
SuppressFileSystems a = targetClass.getAnnotation(SuppressFileSystems.class);
avoid.addAll(Arrays.asList(a.value()));
}
FileSystem fs = FileSystems.getDefault();
if (LuceneTestCase.VERBOSE && allowed(avoid, VerboseFS.class)) {
fs = new VerboseFS(fs, new TestRuleSetupAndRestoreClassEnv.ThreadNameFixingPrintStreamInfoStream(System.out)).getFileSystem(null);
}
Random random = RandomizedContext.current().getRandom();
// speed up tests by omitting actual fsync calls to the hardware most of the time.
if (targetClass.isAnnotationPresent(SuppressFsync.class) || random.nextInt(100) > 0) {
if (allowed(avoid, DisableFsyncFS.class)) {
fs = new DisableFsyncFS(fs).getFileSystem(null);
}
}
// impacts test reproducibility across platforms.
if (random.nextInt(100) > 0) {
if (allowed(avoid, ShuffleFS.class)) {
fs = new ShuffleFS(fs, random.nextLong()).getFileSystem(null);
}
}
// otherwise, wrap with mockfilesystems for additional checks. some
// of these have side effects (e.g. concurrency) so it doesn't always happen.
if (random.nextInt(10) > 0) {
if (allowed(avoid, LeakFS.class)) {
fs = new LeakFS(fs).getFileSystem(null);
}
if (allowed(avoid, HandleLimitFS.class)) {
fs = new HandleLimitFS(fs, MAX_OPEN_FILES).getFileSystem(null);
}
// windows is currently slow
if (random.nextInt(10) == 0) {
// don't try to emulate windows on windows: they don't get along
if (!Constants.WINDOWS && allowed(avoid, WindowsFS.class)) {
fs = new WindowsFS(fs).getFileSystem(null);
}
}
if (allowed(avoid, ExtrasFS.class)) {
fs = new ExtrasFS(fs, random.nextInt(4) == 0, random.nextBoolean()).getFileSystem(null);
}
}
if (LuceneTestCase.VERBOSE) {
System.out.println("filesystem: " + fs.provider());
}
return fs.provider().getFileSystem(URI.create("file:///"));
}
private Path initializeJavaTempDir() throws IOException {
Path javaTempDir = fileSystem.getPath(System.getProperty("tempDir", System.getProperty("java.io.tmpdir")));
Files.createDirectories(javaTempDir);
assert Files.isDirectory(javaTempDir) &&
Files.isWritable(javaTempDir);
return javaTempDir.toRealPath();
}
@Override
protected void afterAlways(List<Throwable> errors) throws Throwable {
// Drain cleanup queue and clear it.
final Path [] everything;
final String tempDirBasePath;
synchronized (cleanupQueue) {
tempDirBasePath = (tempDirBase != null ? tempDirBase.toAbsolutePath().toString() : null);
tempDirBase = null;
Collections.reverse(cleanupQueue);
everything = new Path [cleanupQueue.size()];
cleanupQueue.toArray(everything);
cleanupQueue.clear();
}
// Only check and throw an IOException on un-removable files if the test
// was successful. Otherwise just report the path of temporary files
// and leave them there.
if (failureMarker.wasSuccessful()) {
try {
IOUtils.rm(everything);
} catch (IOException e) {
Class<?> suiteClass = RandomizedContext.current().getTargetClass();
if (suiteClass.isAnnotationPresent(SuppressTempFileChecks.class)) {
System.err.println("WARNING: Leftover undeleted temporary files (bugUrl: "
+ suiteClass.getAnnotation(SuppressTempFileChecks.class).bugUrl() + "): "
+ e.getMessage());
return;
}
throw e;
}
if (fileSystem != FileSystems.getDefault()) {
fileSystem.close();
}
} else {
if (tempDirBasePath != null) {
System.err.println("NOTE: leaving temporary files on disk at: " + tempDirBasePath);
}
}
}
Path getPerTestClassTempDir() {
if (tempDirBase == null) {
RandomizedContext ctx = RandomizedContext.current();
Class<?> clazz = ctx.getTargetClass();
String prefix = clazz.getName();
prefix = prefix.replaceFirst("^org.apache.lucene.", "lucene.");
prefix = prefix.replaceFirst("^org.apache.solr.", "solr.");
int attempt = 0;
Path f;
boolean success = false;
do {
if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD) {
throw new RuntimeException(
"Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: "
+ javaTempDir.toAbsolutePath());
}
f = javaTempDir.resolve(prefix + "_" + ctx.getRunnerSeedAsString()
+ "-" + String.format(Locale.ENGLISH, "%03d", attempt));
try {
Files.createDirectory(f);
success = true;
} catch (IOException ignore) {}
} while (!success);
tempDirBase = f;
registerToRemoveAfterSuite(tempDirBase);
}
return tempDirBase;
}
/**
* @see LuceneTestCase#createTempDir()
*/
public Path createTempDir(String prefix) {
Path base = getPerTestClassTempDir();
int attempt = 0;
Path f;
boolean success = false;
do {
if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD) {
throw new RuntimeException(
"Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: "
+ base.toAbsolutePath());
}
f = base.resolve(prefix + "-" + String.format(Locale.ENGLISH, "%03d", attempt));
try {
Files.createDirectory(f);
success = true;
} catch (IOException ignore) {}
} while (!success);
registerToRemoveAfterSuite(f);
return f;
}
/**
* @see LuceneTestCase#createTempFile()
*/
public Path createTempFile(String prefix, String suffix) throws IOException {
Path base = getPerTestClassTempDir();
int attempt = 0;
Path f;
boolean success = false;
do {
if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD) {
throw new RuntimeException(
"Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: "
+ base.toAbsolutePath());
}
f = base.resolve(prefix + "-" + String.format(Locale.ENGLISH, "%03d", attempt) + suffix);
try {
Files.createFile(f);
success = true;
} catch (IOException ignore) {}
} while (!success);
registerToRemoveAfterSuite(f);
return f;
}
}