| /* |
| * 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; |
| } |
| } |