From 559db4cbce31bd2d1c05c105cfee25fc54430e3c Mon Sep 17 00:00:00 2001 From: Markus Spann Date: Fri, 3 Jun 2022 04:49:39 +0200 Subject: [PATCH] [SUREFIRE-2086] Management of temporary files (#535) --- .../failsafe/MarshallerUnmarshallerTest.java | 3 +- .../maven/plugin/failsafe/RunResultTest.java | 5 +- .../JarManifestForkConfiguration.java | 3 +- .../ModularClasspathForkConfiguration.java | 4 +- .../Utf8RecodingDeferredFileOutputStream.java | 5 +- .../surefire/AbstractSurefireMojoTest.java | 19 +- .../plugin/surefire/MojoMocklessTest.java | 13 +- ...ModularClasspathForkConfigurationTest.java | 4 +- .../runorder/RunEntryStatisticsMapTest.java | 11 +- .../api/util/SureFireFileManager.java | 39 +++ .../surefire/api/util/TempFileManager.java | 242 ++++++++++++++++++ .../api/util/TempFileManagerTest.java | 107 ++++++++ .../maven/surefire/booter/PpidChecker.java | 5 +- .../booter/SystemPropertyManager.java | 4 +- .../surefire/booter/ForkedBooterTest.java | 3 +- 15 files changed, 434 insertions(+), 33 deletions(-) create mode 100644 surefire-api/src/main/java/org/apache/maven/surefire/api/util/SureFireFileManager.java create mode 100644 surefire-api/src/main/java/org/apache/maven/surefire/api/util/TempFileManager.java create mode 100644 surefire-api/src/test/java/org/apache/maven/surefire/api/util/TempFileManagerTest.java diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java index 5df6f7dbcd..20979fd194 100644 --- a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java +++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java @@ -21,6 +21,7 @@ import org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils; import org.apache.maven.surefire.api.suite.RunResult; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.junit.Test; import java.io.File; @@ -73,7 +74,7 @@ public void shouldMarshallAndUnmarshallSameXml() throws Exception + "\n\tat org.apache.maven.plugin.surefire.booterclient.ForkStarter" + ".awaitResultsDone(ForkStarter.java:489)", true ); - File xml = File.createTempFile( "failsafe-summary", ".xml" ); + File xml = SureFireFileManager.createTempFile( "failsafe-summary", ".xml" ); FailsafeSummaryXmlUtils.writeSummary( expected, xml, false ); RunResult actual = FailsafeSummaryXmlUtils.toRunResult( xml ); diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java index 92411b6630..14f7d1762f 100644 --- a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java +++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java @@ -21,6 +21,7 @@ import org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils; import org.apache.maven.surefire.api.suite.RunResult; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.junit.Test; import java.io.File; @@ -84,7 +85,7 @@ public void testAppendSerialization() RunResult simpleAggregate = getSimpleAggregate(); RunResult additional = new RunResult( 2, 1, 2, 2, "msg " + ( (char) 0x0E01 ), true ); - File summary = File.createTempFile( "failsafe", "test" ); + File summary = SureFireFileManager.createTempFile( "failsafe", "test" ); FailsafeSummaryXmlUtils.writeSummary( simpleAggregate, summary, false ); FailsafeSummaryXmlUtils.writeSummary( additional, summary, true ); RunResult actual = FailsafeSummaryXmlUtils.toRunResult( summary ); @@ -121,7 +122,7 @@ public void testAppendSerialization() private void writeReadCheck( RunResult expected ) throws Exception { - File tmp = File.createTempFile( "test", "xml" ); + File tmp = SureFireFileManager.createTempFile( "test", "xml" ); FailsafeSummaryXmlUtils.fromRunResultToFile( expected, tmp ); RunResult actual = FailsafeSummaryXmlUtils.toRunResult( tmp ); diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java index 50caad0552..7f82091eae 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java @@ -25,6 +25,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline; import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton; import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; +import org.apache.maven.surefire.api.util.TempFileManager; import org.apache.maven.surefire.booter.Classpath; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; @@ -113,7 +114,7 @@ private File createJar( @Nonnull List classPath, @Nonnull String startCl @Nonnull File dumpLogDirectory ) throws IOException { - File file = File.createTempFile( "surefirebooter", ".jar", getTempDirectory() ); + File file = TempFileManager.instance( getTempDirectory() ).createTempFile( "surefirebooter", ".jar" ); if ( !isDebug() ) { file.deleteOnExit(); diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java index 0fd796cddb..86c351b638 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java @@ -22,6 +22,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline; import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton; import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; +import org.apache.maven.surefire.api.util.TempFileManager; import org.apache.maven.surefire.booter.AbstractPathConfiguration; import org.apache.maven.surefire.booter.Classpath; import org.apache.maven.surefire.booter.ModularClasspath; @@ -42,7 +43,6 @@ import java.util.Map; import java.util.Properties; -import static java.io.File.createTempFile; import static java.io.File.pathSeparatorChar; import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath; import static org.apache.maven.surefire.api.util.internal.StringUtils.NL; @@ -118,7 +118,7 @@ File createArgsFile( @Nonnull String moduleName, @Nonnull List modulePat @Nonnull List providerJpmsArguments ) throws IOException { - File surefireArgs = createTempFile( "surefireargs", "", getTempDirectory() ); + File surefireArgs = TempFileManager.instance( getTempDirectory() ).createTempFile( "surefireargs", "" ); if ( isDebug() ) { getLogger().debug( "Path to args file: " + surefireArgs.getCanonicalPath() ); diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java index 951e0e1117..a452f75286 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java @@ -32,6 +32,8 @@ import static java.util.Objects.requireNonNull; import static org.apache.maven.surefire.api.util.internal.StringUtils.NL; +import org.apache.maven.surefire.api.util.SureFireFileManager; + /** * A deferred file output stream decorator that recodes the bytes written into the stream from the VM default encoding * to UTF-8. @@ -66,7 +68,8 @@ public synchronized void write( String output, boolean newLine ) if ( storage == null ) { - file = Files.createTempFile( channel, "deferred" ); + + file = SureFireFileManager.createTempFile( channel, "deferred" ).toPath(); storage = new RandomAccessFile( file.toFile(), "rw" ); } diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java index ec00a22a43..2ee2c94b05 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java @@ -59,6 +59,7 @@ import org.apache.maven.repository.RepositorySystem; import org.apache.maven.surefire.api.suite.RunResult; import org.apache.maven.surefire.api.util.DefaultScanResult; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.apache.maven.surefire.booter.ClassLoaderConfiguration; import org.apache.maven.surefire.booter.Classpath; import org.apache.maven.surefire.booter.ModularClasspathConfiguration; @@ -2522,7 +2523,7 @@ public void shouldNotPerformMethodFilteringOnIncludes() throws Exception { Mojo plugin = new Mojo(); - File includesExcludes = File.createTempFile( "surefire", "-includes" ); + File includesExcludes = SureFireFileManager.createTempFile( "surefire", "includes" ); FileUtils.write( includesExcludes, "AnotherTest#method" , UTF_8 ); plugin.setIncludesFile( includesExcludes ); @@ -2533,7 +2534,7 @@ public void shouldNotPerformMethodFilteringOnIncludes() throws Exception VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", ".jar" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", ".jar" ); artifactFile.deleteOnExit(); testDeps.setFile( artifactFile ); plugin.setProjectTestArtifacts( singletonList( testDeps ) ); @@ -2550,14 +2551,14 @@ public void shouldFilterTestsOnIncludesFile() throws Exception plugin.setLogger( mock( Logger.class ) ); - File includes = File.createTempFile( "surefire", "-includes" ); + File includes = SureFireFileManager.createTempFile( "surefire", "includes" ); FileUtils.write( includes, "AnotherTest#method" , UTF_8 ); plugin.setIncludesFile( includes ); VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "test-jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", "-classes" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", "classes" ); String classDir = artifactFile.getCanonicalPath(); assertThat( artifactFile.delete() ).isTrue(); File classes = new File( classDir ); @@ -2580,14 +2581,14 @@ public void shouldFilterTestsOnExcludesFile() throws Exception plugin.setLogger( mock( Logger.class ) ); - File excludes = File.createTempFile( "surefire", "-excludes" ); + File excludes = SureFireFileManager.createTempFile( "surefire", "-excludes" ); FileUtils.write( excludes, "AnotherTest" , UTF_8 ); plugin.setExcludesFile( excludes ); VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "test-jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", "-classes" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", "-classes" ); String classDir = artifactFile.getCanonicalPath(); assertThat( artifactFile.delete() ).isTrue(); File classes = new File( classDir ); @@ -2615,7 +2616,7 @@ public void shouldFilterTestsOnExcludes() throws Exception VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", "-classes" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", "-classes" ); String classDir = artifactFile.getCanonicalPath(); assertThat( artifactFile.delete() ).isTrue(); File classes = new File( classDir ); @@ -2638,7 +2639,7 @@ public void shouldUseOnlySpecificTests() throws Exception plugin.setLogger( mock( Logger.class ) ); - File includes = File.createTempFile( "surefire", "-includes" ); + File includes = SureFireFileManager.createTempFile( "surefire", "-includes" ); FileUtils.write( includes, "AnotherTest" , UTF_8 ); plugin.setIncludesFile( includes ); plugin.setTest( "DifferentTest" ); @@ -2646,7 +2647,7 @@ public void shouldUseOnlySpecificTests() throws Exception VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "test-jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", "-classes" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", "-classes" ); String classDir = artifactFile.getCanonicalPath(); assertThat( artifactFile.delete() ).isTrue(); File classes = new File( classDir ); diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java index 6ad04160cc..4ca4b325d3 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java @@ -31,6 +31,7 @@ import org.apache.maven.surefire.extensions.ForkNodeFactory; import org.apache.maven.surefire.api.suite.RunResult; import org.apache.maven.surefire.api.util.DefaultScanResult; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.apache.maven.toolchain.Toolchain; import org.codehaus.plexus.logging.Logger; import org.junit.Test; @@ -213,7 +214,7 @@ public void scanDependenciesShouldReturnNullWithExistingWAR() VersionRange version = VersionRange.createFromVersion( "1.0" ); ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "war", null, handler ); - File artifactFile = File.createTempFile( "surefire", ".war" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", ".war" ); testDeps.setFile( artifactFile ); List projectTestArtifacts = singletonList( testDeps ); String[] dependenciesToScan = { "g:a" }; @@ -231,7 +232,7 @@ public void scanDependenciesShouldReturnClassWithExistingTestJAR() ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "test-jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", ".jar" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", ".jar" ); testDeps.setFile( artifactFile ); try ( ZipOutputStream os = new ZipOutputStream( new FileOutputStream( artifactFile ) ) ) { @@ -266,7 +267,7 @@ public void scanDependenciesShouldReturnNullWithEmptyTestJAR() ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", ".jar" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", ".jar" ); testDeps.setFile( artifactFile ); try ( ZipOutputStream os = new ZipOutputStream( new FileOutputStream( artifactFile ) ) ) { @@ -296,7 +297,7 @@ public void scanDependenciesShouldReturnClassWithDirectory() ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDeps = new DefaultArtifact( "g", "a", version, "compile", "test-jar", null, handler ); - File artifactFile = File.createTempFile( "surefire", "-classes" ); + File artifactFile = SureFireFileManager.createTempFile( "surefire", "-classes" ); String classDir = artifactFile.getCanonicalPath(); assertThat( artifactFile.delete() ).isTrue(); File classes = new File( classDir ); @@ -331,7 +332,7 @@ public void scanMultipleDependencies() ArtifactHandler handler = new DefaultArtifactHandler(); Artifact testDep1 = new DefaultArtifact( "g", "x", version, "compile", "jar", null, handler ); - File artifactFile1 = File.createTempFile( "surefire", "-classes" ); + File artifactFile1 = SureFireFileManager.createTempFile( "surefire", "-classes" ); String classDir = artifactFile1.getCanonicalPath(); assertThat( artifactFile1.delete() ).isTrue(); File classes = new File( classDir ); @@ -343,7 +344,7 @@ public void scanMultipleDependencies() .isTrue(); Artifact testDep2 = new DefaultArtifact( "g", "a", version, "test", "jar", null, handler ); - File artifactFile2 = File.createTempFile( "surefire", ".jar" ); + File artifactFile2 = SureFireFileManager.createTempFile( "surefire", ".jar" ); testDep2.setFile( artifactFile2 ); try ( ZipOutputStream os = new ZipOutputStream( new FileOutputStream( artifactFile2 ) ) ) { diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java index 668859185c..7f257c0225 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java @@ -21,6 +21,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline; import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.apache.maven.surefire.booter.ClassLoaderConfiguration; import org.apache.maven.surefire.booter.Classpath; import org.apache.maven.surefire.booter.ForkedBooter; @@ -36,7 +37,6 @@ import java.util.List; import java.util.Properties; -import static java.io.File.createTempFile; import static java.io.File.pathSeparatorChar; import static java.io.File.separator; import static java.io.File.separatorChar; @@ -147,7 +147,7 @@ public void shouldCreateModularArgsFile() throws Exception modularClasspathConfiguration, clc, null, Collections.emptyList() ); Commandline cli = new Commandline(); config.resolveClasspath( cli, ForkedBooter.class.getName(), startupConfiguration, - createTempFile( "surefire", "surefire-reports" ) ); + SureFireFileManager.createTempFile( "surefire", "surefire-reports" ) ); assertThat( cli.getArguments() ).isNotNull(); assertThat( cli.getArguments() ).hasSize( 1 ); diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java index 8f5f580543..8fd7535e93 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java @@ -35,6 +35,7 @@ import org.apache.maven.surefire.api.report.SimpleReportEntry; import junit.framework.TestCase; +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.apache.maven.surefire.api.util.internal.ClassMethod; import static java.nio.charset.StandardCharsets.UTF_8; @@ -85,7 +86,7 @@ private InputStream getStatisticsFile() public void testSerializeClass() throws Exception { - File data = File.createTempFile( "surefire-unit", "test" ); + File data = SureFireFileManager.createTempFile( "surefire-unit", "test" ); RunEntryStatisticsMap newResults = new RunEntryStatisticsMap(); ReportEntry reportEntry = new SimpleReportEntry( NORMAL_RUN, 0L, "abc", null, null, null, 42 ); @@ -107,7 +108,7 @@ public void testSerializeClass() public void testDeserializeClass() throws Exception { - File data = File.createTempFile( "surefire-unit", "test" ); + File data = SureFireFileManager.createTempFile( "surefire-unit", "test" ); Files.write( data.toPath(), "1,42,abc".getBytes( UTF_8 ) ); RunEntryStatisticsMap existingEntries = RunEntryStatisticsMap.fromFile( data ); Map runEntryStatistics = getInternalState( existingEntries, "runEntryStatistics" ); @@ -129,7 +130,7 @@ public void testDeserializeClass() public void testSerialize() throws Exception { - File data = File.createTempFile( "surefire-unit", "test" ); + File data = SureFireFileManager.createTempFile( "surefire-unit", "test" ); RunEntryStatisticsMap existingEntries = RunEntryStatisticsMap.fromFile( data ); RunEntryStatisticsMap newResults = new RunEntryStatisticsMap(); @@ -186,7 +187,7 @@ public void testSerialize() @SuppressWarnings( "checkstyle:magicnumber" ) public void testMultiLineTestMethodName() throws IOException { - File data = File.createTempFile( "surefire-unit", "test" ); + File data = SureFireFileManager.createTempFile( "surefire-unit", "test" ); RunEntryStatisticsMap reportEntries = RunEntryStatisticsMap.fromFile( data ); ReportEntry reportEntry = new SimpleReportEntry( NORMAL_RUN, 0L, "abc", null, "line1\nline2" + NL + " line3", null, 42 ); @@ -222,7 +223,7 @@ public void testMultiLineTestMethodName() throws IOException @SuppressWarnings( "checkstyle:magicnumber" ) public void testCombinedMethodNames() throws IOException { - File data = File.createTempFile( "surefire-unit", "test" ); + File data = SureFireFileManager.createTempFile( "surefire-unit", "test" ); RunEntryStatisticsMap reportEntries = RunEntryStatisticsMap.fromFile( data ); reportEntries.add( reportEntries.createNextGeneration( new SimpleReportEntry( NORMAL_RUN, 0L, "abc", null, "line1\nline2", null, 42 ) ) ); diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/SureFireFileManager.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/SureFireFileManager.java new file mode 100644 index 0000000000..212f299859 --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/SureFireFileManager.java @@ -0,0 +1,39 @@ +package org.apache.maven.surefire.api.util; + +/* + * 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. + */ + +import java.io.File; + +/** + * Centralized file management of surefire. + * + * @author Markus Spann + */ +public final class SureFireFileManager +{ + + public static File createTempFile( String prefix, String suffix ) + { + + return TempFileManager.instance( "surefire" ).createTempFile( prefix, suffix ); + + } + +} diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/TempFileManager.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/TempFileManager.java new file mode 100644 index 0000000000..63d3b7f3df --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/TempFileManager.java @@ -0,0 +1,242 @@ +package org.apache.maven.surefire.api.util; + +/* + * 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. + */ + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Management of temporary files in surefire with support for sub directories of the system's directory + * of temporary files.
+ * The {@link File#createTempFile(String, String)} API creates rather meaningless file names and + * only in the system's temp directory.
+ * This class creates temp files from a prefix, a unique date/timestamp, a short file id and suffix. + * It also helps you organize temporary files into sub-directories, + * and thus avoid bloating the temp directory root.
+ * Apart from that, this class works in much the same way as {@link File#createTempFile(String, String)} + * and {@link File#deleteOnExit()}, and can be used as a drop-in replacement. + * + * @author Markus Spann + */ +public final class TempFileManager +{ + + private static final Map INSTANCES = new LinkedHashMap<>(); + /** An id unique across all file managers used as part of the temporary file's base name. */ + private static final AtomicInteger FILE_ID = new AtomicInteger( 1 ); + private static final String SUFFIX_TMP = ".tmp"; + + private static Thread shutdownHook; + + /** The temporary directory or sub-directory under which temporary files are created. */ + private final File tempDir; + /** The fixed base name used between prefix and suffix of temporary files created by this class. */ + private final String baseName; + + /** List of successfully created temporary files. */ + private final List tempFiles; + + /** Temporary files are delete on JVM exit if true. */ + private boolean deleteOnExit; + + private TempFileManager( File tempDir ) + { + this.tempDir = tempDir; + this.baseName = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS" ).format( LocalDateTime.now() ); + this.tempFiles = new ArrayList<>(); + } + + private static File calcTempDir( String subDirName ) + { + String tempDirName = subDirName == null ? null + : subDirName.trim().isEmpty() ? null : subDirName.trim(); + File tempDirCandidate = tempDirName == null ? new File( getJavaIoTmpDir() ) + : new File( getJavaIoTmpDir(), tempDirName ); + return tempDirCandidate; + } + + public static TempFileManager instance( ) + { + return instance( ( File ) null ); + } + + /** + * Creates an instance using a subdirectory of the system's temporary directory. + * @param subDirName name of subdirectory + * @return instance + */ + public static TempFileManager instance( String subDirName ) + { + return instance( calcTempDir( subDirName ) ); + } + + public static synchronized TempFileManager instance( File tempDir ) + { + + // on creation of the first instance, register a shutdown hook to delete temporary files of all managers + if ( shutdownHook == null ) + { + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + while ( tg.getParent() != null ) + { + tg = tg.getParent(); + } + shutdownHook = new Thread( tg, TempFileManager::shutdownAll, + TempFileManager.class.getSimpleName() + "-ShutdownHook" ); + Runtime.getRuntime().addShutdownHook( shutdownHook ); + } + + return INSTANCES.computeIfAbsent( + tempDir == null ? new File( getJavaIoTmpDir() ) : tempDir, + TempFileManager::new ); + } + + public synchronized void deleteAll() + { + tempFiles.forEach( File::delete ); + tempFiles.clear(); + tempDir.delete(); + } + + static void shutdownAll() + { + INSTANCES.values().stream() + .filter( TempFileManager::isDeleteOnExit ) + .forEach( TempFileManager::deleteAll ); + } + + /** + * Returns the temporary directory or sub-directory under which temporary files are created. + * + * @return temporary directory + */ + public File getTempDir() + { + return tempDir; + } + + /** + * Creates a new, uniquely-named temporary file in this object's {@link #tempDir} + * with user-defined prefix and suffix (both may be null or empty and won't be trimmed). + *

+ * This method behaves similarly to {@link java.io.File#createTempFile(String, String)} and + * may be used as a drop-in replacement.
+ * This method is {@code synchronized} to help ensure uniqueness of temporary files. + * + * @param prefix optional file name prefix + * @param suffix optional file name suffix + * @return file object + */ + public synchronized File createTempFile( String prefix, String suffix ) + { + // use only the file name from the supplied prefix + prefix = prefix == null ? "" : ( new File( prefix ) ).getName(); + suffix = suffix == null ? "" : suffix; + + String name = String.join( "-", prefix, baseName + "_" + FILE_ID.getAndIncrement() ) + suffix; + File tempFile = new File( tempDir, name ); + if ( !name.equals( tempFile.getName() ) ) + { + throw new UncheckedIOException( new IOException( "Unable to create temporary file " + tempFile ) ); + } + + if ( tempFile.exists() || tempFiles.contains( tempFile ) ) + { + // temporary file already exists? Try another name + return createTempFile( prefix, suffix ); + } + else + { + // create temporary directory + if ( !tempDir.exists() ) + { + if ( !tempDir.mkdirs() ) + { + throw new UncheckedIOException( new IOException( + "Unable to create temporary directory " + tempDir.getAbsolutePath() ) ); + } + } + + try + { + tempFile.createNewFile(); + } + catch ( IOException ex ) + { + throw new UncheckedIOException( "Unable to create temporary file " + tempFile.getAbsolutePath(), ex ); + } + + tempFiles.add( tempFile ); + return tempFile; + } + } + + public File createTempFile( String prefix ) + { + return createTempFile( prefix, SUFFIX_TMP ); + } + + public boolean isDeleteOnExit() + { + return deleteOnExit; + } + + /** + * Instructs this file manager to delete its temporary files during JVM shutdown.
+ * This status can be turned on and off unlike {@link File#deleteOnExit()}. + * + * @param deleteOnExit delete the file in a shutdown hook on JVM exit + */ + public void setDeleteOnExit( boolean deleteOnExit ) + { + this.deleteOnExit = deleteOnExit; + } + + @Override + public String toString() + { + return String.format( "%s[tempDir=%s, deleteOnExit=%s, baseName=%s, tempFiles=%d]", + getClass().getSimpleName(), tempDir, deleteOnExit, baseName, tempFiles.size() ); + } + + /** + * Returns the value of system property {@code java.io.tmpdir}, the system's temp directory. + * + * @return path to system temp directory + */ + public static String getJavaIoTmpDir() + { + String tmpDir = System.getProperty( "java.io.tmpdir" ); + if ( !tmpDir.endsWith( File.separator ) ) + { + tmpDir += File.separatorChar; + } + return tmpDir; + } + +} diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/util/TempFileManagerTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/TempFileManagerTest.java new file mode 100644 index 0000000000..d8e54f9eee --- /dev/null +++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/TempFileManagerTest.java @@ -0,0 +1,107 @@ +package org.apache.maven.surefire.api.util; + +/* + * 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. + */ + +import static org.assertj.core.api.Assertions.assertThat; +import static org.powermock.reflect.Whitebox.getInternalState; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Random; + +/** + * Unit test for the surefire temp file manager. + * + * @author Markus Spann + */ +public class TempFileManagerTest extends TestCase +{ + + @Test + public void testDefaultInstance() + { + TempFileManager tfm = TempFileManager.instance(); + assertSame( tfm, TempFileManager.instance( (File) null ) ); + assertSame( tfm, TempFileManager.instance( (String) null ) ); + assertSame( tfm, TempFileManager.instance( new File( System.getProperty( "java.io.tmpdir" ) ) ) ); + + assertThat( tfm.getTempDir() ).isEqualTo( new File( System.getProperty( "java.io.tmpdir" ) ) ); + assertThat( tfm.toString() ).contains( "tempDir=" + tfm.getTempDir().getPath() ); + assertThat( tfm.toString() ).contains( "baseName=" + getInternalState( tfm, "baseName" ) ); + } + + @Test + public void testSubdirInstance() + { + String subDirName = TempFileManagerTest.class.getSimpleName() + new Random().nextLong(); + TempFileManager tfm = TempFileManager.instance( subDirName ); + assertEquals( tfm.getTempDir(), new File( System.getProperty( "java.io.tmpdir" ), subDirName ) ); + assertFalse( tfm.getTempDir() + " should not exist", tfm.getTempDir().exists() ); + } + + @Test + public void testCreateTempFileAndDelete() + { + String subDirName = TempFileManagerTest.class.getSimpleName() + new Random().nextLong(); + TempFileManager tfm = TempFileManager.instance( subDirName ); + String prefix = "prefix"; + String suffix = "suffix"; + File tempFile = tfm.createTempFile( prefix, suffix ); + assertThat( tempFile ).exists(); + assertThat( tempFile ).isWritable(); + assertTrue( tempFile.getParentFile().equals( tfm.getTempDir() ) ); + assertThat( tempFile.getName() ).startsWith( prefix ); + assertThat( tempFile.getName() ).endsWith( suffix ); + assertThat( tempFile.getName() ).contains( (String) getInternalState( tfm, "baseName" ) ); + + List tempFiles = getInternalState( tfm, "tempFiles" ); + assertThat( tempFiles ).contains( tempFile ); + assertThat( tempFiles ).size().isEqualTo( 1 ); + + tfm.deleteAll(); + assertThat( tempFile ).doesNotExist(); + assertThat( tfm.getTempDir() ).doesNotExist(); + } + + @Test + public void testFileCreateTempFile() throws IOException + { + File tempFile = File.createTempFile( "TempFileManager", ".tmp" ); + assertTrue( tempFile.exists() ); + assertTrue( tempFile.delete() ); + assertFalse( tempFile.exists() ); + } + + @Test + public void testDeleteOnExit() + { + TempFileManager tfm = TempFileManager.instance(); + + assertFalse( tfm.isDeleteOnExit() ); + tfm.setDeleteOnExit( true ); + assertTrue( tfm.isDeleteOnExit() ); + } + + +} diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java index 1fb8a30aea..e21264bd48 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java @@ -20,6 +20,7 @@ */ import org.apache.maven.surefire.api.booter.DumpErrorSingleton; +import org.apache.maven.surefire.api.util.SureFireFileManager; import javax.annotation.Nonnull; import java.io.File; @@ -38,7 +39,6 @@ import static java.lang.Integer.parseInt; import static java.lang.Long.parseLong; import static java.lang.String.join; -import static java.nio.file.Files.createTempFile; import static java.nio.file.Files.delete; import static java.nio.file.Files.readAllBytes; import static java.util.concurrent.TimeUnit.DAYS; @@ -407,7 +407,8 @@ ProcessInfo execute( String... command ) Path stdErr = null; try { - stdErr = createTempFile( "surefire", null ); + stdErr = SureFireFileManager.createTempFile( "surefire", null ).toPath(); + processBuilder.redirectError( stdErr.toFile() ); if ( IS_OS_HP_UX ) // force to run shell commands in UNIX Standard mode on HP-UX { diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java index abed5e8928..68accca657 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java @@ -28,6 +28,8 @@ import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; +import org.apache.maven.surefire.api.util.TempFileManager; + /** * @author Kristian Rosenvold */ @@ -74,7 +76,7 @@ public static File writePropertiesFile( Properties properties, File tempDirector boolean keepForkFiles ) throws IOException { - File file = File.createTempFile( name, "tmp", tempDirectory ); + File file = TempFileManager.instance( tempDirectory ).createTempFile( name, "tmp" ); if ( !keepForkFiles ) { file.deleteOnExit(); diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java index b5a1851ea4..a710824158 100644 --- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java @@ -19,6 +19,7 @@ * under the License. */ +import org.apache.maven.surefire.api.util.SureFireFileManager; import org.apache.maven.surefire.shared.io.FileUtils; import org.junit.Test; @@ -202,7 +203,7 @@ public void testPropsNotExist() throws Exception @Test public void testPropsExist() throws Exception { - File props = File.createTempFile( "surefire", ".properties" ); + File props = SureFireFileManager.createTempFile( "surefire", ".properties" ); String target = props.getParent(); String file = props.getName(); FileUtils.write( props, "Hi", StandardCharsets.US_ASCII );