blob: 59564e6b410e270ceb18e25e7df03c04100cacdc [file] [log] [blame]
// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_FROM_INPUT_STREAM;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_TO_OUTPUT_STREAM;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.MOVE;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.EXISTING;
import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.NEW;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.List;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests Files#copy and Files#move methods. Known limitations/details below 26: - ATOMIC MOVE is a
* no-op, moves are effectively file renaming which seem to be atomic on Android. - COPY_ATTRIBUTES
* is a no-op, except if unsupported in which case it throws appropriately, now it seems only basic
* file attributes are supported below 26 anyway and such attributes are not meaningfully copied
* around anyway. Creation/last modification/last access times are set to the time of the move/copy,
* and the other ones such as "isDirectory" cannot be easily abused.
*/
@RunWith(Parameterized.class)
public class FilesMoveCopyTest extends DesugaredLibraryTestBase {
private static final String EXPECTED_RESULT_FORMAT =
StringUtils.lines(
"Testing COPY_TO_OUTPUT_STREAM EXISTING NONE",
"COPY_TO_OUTPUT_STREAM_NONE",
"Testing MOVE NEW NONE",
"MOVE_NONE",
"Testing COPY NEW NONE",
"COPY_NONE",
"Testing COPY_FROM_INPUT_STREAM NEW NONE",
"COPY_FROM_INPUT_STREAM_NONE",
"Testing MOVE EXISTING NONE",
"Failure: class java.nio.file.FileAlreadyExistsException :: dest",
"Testing COPY EXISTING NONE",
"Failure: class java.nio.file.FileAlreadyExistsException :: dest",
"Testing COPY_FROM_INPUT_STREAM EXISTING NONE",
"%s",
"Testing MOVE NEW ATOMIC_MOVE",
"MOVE_ATOMIC_MOVE",
"Testing COPY NEW ATOMIC_MOVE",
"Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
"Testing COPY_FROM_INPUT_STREAM NEW ATOMIC_MOVE",
"Failure: class java.lang.UnsupportedOperationException :: ATOMIC_MOVE not supported",
"Testing MOVE NEW COPY_ATTRIBUTES",
"Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
"Testing COPY NEW COPY_ATTRIBUTES",
"COPY_COPY_ATTRIBUTES",
"Testing COPY_FROM_INPUT_STREAM NEW COPY_ATTRIBUTES",
"Failure: class java.lang.UnsupportedOperationException :: COPY_ATTRIBUTES not supported",
"Testing MOVE NEW REPLACE_EXISTING",
"MOVE_REPLACE_EXISTING",
"Testing COPY NEW REPLACE_EXISTING",
"COPY_REPLACE_EXISTING",
"Testing COPY_FROM_INPUT_STREAM NEW REPLACE_EXISTING",
"COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
"Testing MOVE EXISTING REPLACE_EXISTING",
"MOVE_REPLACE_EXISTING",
"Testing COPY EXISTING REPLACE_EXISTING",
"COPY_REPLACE_EXISTING",
"Testing COPY_FROM_INPUT_STREAM EXISTING REPLACE_EXISTING",
"COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
"Testing MOVE NEW NOFOLLOW_LINKS",
"MOVE_NOFOLLOW_LINKS",
"Testing COPY NEW NOFOLLOW_LINKS",
"COPY_NOFOLLOW_LINKS",
"Testing COPY_FROM_INPUT_STREAM NEW NOFOLLOW_LINKS",
"Failure: class java.lang.UnsupportedOperationException :: NOFOLLOW_LINKS not supported");
private final TestParameters parameters;
private final LibraryDesugaringSpecification libraryDesugaringSpecification;
private final CompilationSpecification compilationSpecification;
@Parameters(name = "{0}, spec: {1}, {2}")
public static List<Object[]> data() {
return buildParameters(
// Skip Android 4.4.4 due to missing libjavacrypto.
getTestParameters()
.withCfRuntime(CfVm.JDK11)
.withDexRuntimesStartingFromIncluding(Version.V8_1_0)
.withAllApiLevels()
.build(),
ImmutableList.of(JDK11_PATH),
DEFAULT_SPECIFICATIONS);
}
public FilesMoveCopyTest(
TestParameters parameters,
LibraryDesugaringSpecification libraryDesugaringSpecification,
CompilationSpecification compilationSpecification) {
this.parameters = parameters;
this.libraryDesugaringSpecification = libraryDesugaringSpecification;
this.compilationSpecification = compilationSpecification;
}
private String getExpectedResult() {
// TODO(b/260208125): Fix invalid behavior.
// When copying from the input stream into an existing file and no options,
// the desugared version succeeds by deleting the file while the regular version fails
// with FileAlreadyExistsException exception.
String result =
(parameters.isDexRuntime()
&& !libraryDesugaringSpecification.usesPlatformFileSystem(parameters))
? "COPY_FROM_INPUT_STREAM_NONE"
: "Failure: class java.nio.file.FileAlreadyExistsException :: dest";
return String.format(EXPECTED_RESULT_FORMAT, result);
}
@Test
public void test() throws Throwable {
if (parameters.isCfRuntime()) {
// Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
// behaves differently on this test.
Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
testForJvm()
.addInnerClasses(getClass())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(getExpectedResult());
return;
}
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.compile()
.withArt6Plus64BitsLib()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(getExpectedResult());
}
public static class TestClass {
enum CopyOrMove {
COPY,
COPY_FROM_INPUT_STREAM,
COPY_TO_OUTPUT_STREAM,
MOVE
}
enum NewFile {
EXISTING,
NEW
}
public static void main(String[] args) throws Throwable {
test(COPY_TO_OUTPUT_STREAM, EXISTING, null);
for (CopyOption copyOption : allOptions()) {
test(MOVE, NEW, copyOption);
test(COPY, NEW, copyOption);
test(COPY_FROM_INPUT_STREAM, NEW, copyOption);
if (copyOption == null || copyOption == StandardCopyOption.REPLACE_EXISTING) {
test(MOVE, EXISTING, copyOption);
test(COPY, EXISTING, copyOption);
test(COPY_FROM_INPUT_STREAM, EXISTING, copyOption);
}
}
}
private static CopyOption[] allOptions() {
return new CopyOption[] {
null,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING,
LinkOption.NOFOLLOW_LINKS
};
}
private static void clearTmp(Path src, Path dest) throws IOException {
if (Files.exists(src)) {
Files.delete(src);
}
if (Files.exists(dest)) {
Files.delete(dest);
}
}
private static void test(CopyOrMove copyOrMove, NewFile newFile, CopyOption copyOption)
throws IOException {
String copyOptionString = copyOption == null ? "NONE" : copyOption.toString();
CopyOption[] opts = copyOption == null ? new CopyOption[0] : new CopyOption[] {copyOption};
System.out.println("Testing " + copyOrMove + " " + newFile + " " + copyOptionString);
Path src = Files.createTempFile("src_", ".txt");
Path dest =
newFile == NEW
? src.getParent().resolve("dest_" + System.currentTimeMillis() + ".txt")
: Files.createTempFile("dest_", ".txt");
String toWrite = copyOrMove + "_" + copyOptionString;
Files.write(src, toWrite.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
try {
switch (copyOrMove) {
case COPY:
Files.copy(src, dest, opts);
break;
case MOVE:
Files.move(src, dest, opts);
break;
case COPY_FROM_INPUT_STREAM:
FileInputStream in = new FileInputStream(src.toFile());
Files.copy(in, dest, opts);
in.close();
break;
case COPY_TO_OUTPUT_STREAM:
if (opts.length != 0) {
throw new UnsupportedOperationException("Api does not encode options");
}
FileOutputStream out = new FileOutputStream(dest.toFile());
Files.copy(src, out);
out.close();
break;
}
System.out.println(Files.readAllLines(dest).get(0));
} catch (Throwable t) {
String subMessage;
if (t instanceof FileAlreadyExistsException && t.getMessage() != null) {
// The message is the file path, however we cannot rely on temp path, so we check
// only against the known part of the path.
String[] split = t.getMessage().split("/");
subMessage = split[split.length - 1].substring(0, 4);
} else {
subMessage = t.getMessage();
}
System.out.println("Failure: " + t.getClass() + " :: " + subMessage);
}
clearTmp(src, dest);
}
}
}