Path: blob/master/test/jdk/java/nio/file/Files/SubstDrive.java
41153 views
/*1* Copyright (c) 2020 Microsoft Corporation. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*22*/2324import java.util.Map;25import java.util.Optional;26import java.util.stream.Stream;27import java.io.IOException;28import java.io.UnsupportedEncodingException;29import java.io.ByteArrayOutputStream;30import java.io.PrintStream;31import java.nio.charset.StandardCharsets;32import java.nio.file.*;3334import static org.testng.Assert.*;35import org.testng.annotations.BeforeClass;36import org.testng.annotations.AfterClass;37import org.testng.annotations.AfterMethod;38import org.testng.annotations.Test;39import org.testng.SkipException;4041import jdk.test.lib.process.ProcessTools;42import jdk.test.lib.process.OutputAnalyzer;4344/* @test45* @summary Test Files' public APIs with drives created using the subst command on Windows.46* @requires (os.family == "windows")47* @library /test/lib ..48* @build SubstDrive49* @run testng SubstDrive50*/51public class SubstDrive {5253private static Path SUBST_DRIVE;54private static Path TEST_TEMP_DIRECTORY;5556/**57* Setup for the test:58* + Create a temporary directory where all subsequently created temp59* directories will be in. This directory and all of its contents will be60* deleted when the test finishes.61* + Find a drive that is available for use with subst.62*/63@BeforeClass64public void setup() throws IOException {65TEST_TEMP_DIRECTORY = Files.createTempDirectory("tmp");66System.out.printf("Test directory is at %s\n", TEST_TEMP_DIRECTORY);6768Optional<Path> substDrive = findAvailableDrive(TEST_TEMP_DIRECTORY);69if (!substDrive.isPresent()) {70throw new SkipException(71"Could not find any available drive to use with subst, skipping the tests");72}73SUBST_DRIVE = substDrive.get();74System.out.printf("Using drive %s\n with subst", SUBST_DRIVE);75}7677/**78* Delete the root temporary directory together with all of its contents79* when all tests finish.80*/81@AfterClass82public void removeRootTempDirectory() throws IOException {83TestUtil.removeAll(TEST_TEMP_DIRECTORY);84}8586/**87* Each test method maps drive `SUBST_DRIVE` to a temporary directory,88* unmap the drive after every test so that subsequent ones can reuse89* the drive.90*/91@AfterMethod92public void deleteSubstDrive() throws IOException {93Stream<String> substitutedDrives = substFindMappedDrives();94// Only delete `SUBST_DRIVE` if it is currently being substituted95if (substitutedDrives.anyMatch(e -> e.contains(SUBST_DRIVE.toString()))) {96substDelete(SUBST_DRIVE);97}98}99100/**101* Test whether files can be created in the substituted drive.102*/103@Test104public void testCreateAndDeleteFile() throws IOException {105Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");106substCreate(SUBST_DRIVE, tempDirectory);107108String fileContents = "Hello world!";109Path p = Path.of(SUBST_DRIVE.toString(), "testFile.txt");110Files.createFile(p);111112assertTrue(Files.exists(p));113114Files.writeString(p, fileContents);115assertEquals(Files.readString(p), fileContents);116}117118/**119* Test if we can delete the substituted drive (essentially just a directory).120*/121@Test122public void testDeleteSubstitutedDrive() throws IOException {123Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");124substCreate(SUBST_DRIVE, tempDirectory);125126assertTrue(Files.exists(tempDirectory));127Files.delete(SUBST_DRIVE);128assertTrue(Files.notExists(tempDirectory));129}130131/**132* Test if the attributes returned by the Files' APIs are consistent when133* using the actual path and the substituted path.134*/135@Test136public void testAttributes() throws IOException {137Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");138substCreate(SUBST_DRIVE, tempDirectory);139140assertTrue(Files.isSameFile(tempDirectory, SUBST_DRIVE));141142assertEquals(143Files.isExecutable(tempDirectory),144Files.isExecutable(SUBST_DRIVE));145146assertEquals(147Files.isReadable(tempDirectory),148Files.isReadable(SUBST_DRIVE));149150assertEquals(151Files.isDirectory(tempDirectory),152Files.isDirectory(SUBST_DRIVE));153154assertEquals(155Files.isHidden(tempDirectory),156Files.isHidden(SUBST_DRIVE));157158assertEquals(159Files.isRegularFile(tempDirectory),160Files.isRegularFile(SUBST_DRIVE));161162assertEquals(163Files.isSymbolicLink(tempDirectory),164Files.isSymbolicLink(SUBST_DRIVE));165166assertEquals(167Files.getOwner(tempDirectory),168Files.getOwner(SUBST_DRIVE));169170assertEquals(171Files.isWritable(tempDirectory),172Files.isWritable(SUBST_DRIVE));173}174175/**176* Test if setting attributes for a substituted path works the same way177* as it would for a real path.178*/179@Test180public void testGetSetAttributes() throws IOException {181Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");182substCreate(SUBST_DRIVE, tempDirectory);183184Files.setAttribute(SUBST_DRIVE, "dos:hidden", true);185assertTrue(Files.isHidden(SUBST_DRIVE));186assertTrue(Files.isHidden(tempDirectory));187188Files.setAttribute(tempDirectory, "dos:hidden", false);189assertFalse(Files.isHidden(SUBST_DRIVE));190assertFalse(Files.isHidden(tempDirectory));191192Map<String, Object> attr1 = Files.readAttributes(SUBST_DRIVE, "*");193Map<String, Object> attr2 = Files.readAttributes(tempDirectory, "*");194assertEquals(attr1, attr2);195}196197/**198* Test if the FileStores returned from using substituted path and real path199* are the same.200*/201@Test202public void testFileStore() throws IOException {203Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");204substCreate(SUBST_DRIVE, tempDirectory);205206FileStore fileStore1 = Files.getFileStore(tempDirectory);207FileStore fileStore2 = Files.getFileStore(SUBST_DRIVE);208209assertEquals(210fileStore1.getTotalSpace(),211fileStore2.getTotalSpace());212213assertEquals(214fileStore1.getBlockSize(),215fileStore2.getBlockSize());216217assertEquals(218fileStore1.name(),219fileStore2.name());220221assertEquals(222fileStore1.type(),223fileStore2.type());224225assertEquals(226SUBST_DRIVE.getFileSystem().getRootDirectories(),227tempDirectory.getFileSystem().getRootDirectories());228}229230/**231* Test if Files.copy works correctly on a substituted drive, and that232* all of the attributes are the same.233*/234@Test235public void testMoveAndCopySubstDrive() throws IOException {236Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");237Path tempDirectoryCopy = Path.of(tempDirectory.toString() + "_copy");238239substCreate(SUBST_DRIVE, tempDirectory);240241Files.copy(SUBST_DRIVE, tempDirectoryCopy);242243assertEquals(244Files.isExecutable(SUBST_DRIVE),245Files.isExecutable(tempDirectoryCopy));246247assertEquals(248Files.isReadable(SUBST_DRIVE),249Files.isReadable(tempDirectoryCopy));250251assertEquals(252Files.isDirectory(SUBST_DRIVE),253Files.isDirectory(tempDirectoryCopy));254255assertEquals(256Files.isHidden(SUBST_DRIVE),257Files.isHidden(tempDirectoryCopy));258259assertEquals(260Files.isRegularFile(SUBST_DRIVE),261Files.isRegularFile(tempDirectoryCopy));262263assertEquals(264Files.isWritable(SUBST_DRIVE),265Files.isWritable(tempDirectoryCopy));266267assertEquals(268Files.getOwner(SUBST_DRIVE),269Files.getOwner(tempDirectoryCopy));270}271272/**273* Test if the attributes of a resolved symlink are the same as its target's274* Note: requires administrator privileges.275*/276@Test277public void testGetResolvedSymlinkAttribute() throws IOException {278if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {279return;280}281282Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");283substCreate(SUBST_DRIVE, tempDirectory);284285Path tempFile = Path.of(SUBST_DRIVE.toString(), "test.txt");286String contents = "Hello world!";287Files.writeString(tempFile, contents);288assertEquals(Files.readString(tempFile), contents);289290Path link = Path.of(SUBST_DRIVE.toString(), "link");291Files.createSymbolicLink(link, tempFile);292293assertEquals(Files.readString(link), contents);294assertEquals(Files.isExecutable(link), Files.isExecutable(tempFile));295assertEquals(Files.isReadable(link), Files.isReadable(tempFile));296assertEquals(Files.isDirectory(link), Files.isDirectory(tempFile));297assertEquals(Files.isHidden(link), Files.isHidden(tempFile));298assertEquals(Files.isRegularFile(link), Files.isRegularFile(tempFile));299assertEquals(Files.isWritable(link), Files.isWritable(tempFile));300assertEquals(Files.getOwner(link), Files.getOwner(tempFile));301}302303/**304* Test if files and directories can be created, moved, and cut when the305* substituted drive is a symlink.306* Note: requires administrator privileges.307*/308@Test309public void testSubstWithSymlinkedDirectory() throws IOException {310if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {311return;312}313314Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");315Path tempLink = Path.of(tempDirectory.toString() + "_link");316Files.createSymbolicLink(tempLink, tempDirectory);317318substCreate(SUBST_DRIVE, tempLink);319320assertEquals(321Files.readAttributes(SUBST_DRIVE, "*"),322Files.readAttributes(tempDirectory, "*"));323324assertTrue(Files.isWritable(SUBST_DRIVE));325326Path tempFile = Files.createTempFile(SUBST_DRIVE, "prefix", "suffix");327String contents = "Hello world!";328Files.writeString(tempFile, contents);329assertEquals(Files.readString(tempFile), contents);330331Path tempDirectory2 = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");332Path copy = Path.of(tempDirectory2.toString(), "copied");333Files.copy(tempFile, copy);334335assertTrue(Files.exists(copy));336assertEquals(Files.readString(copy), contents);337338Path cut = Path.of(tempDirectory2.toString(), "cut");339Files.move(tempFile, cut);340assertTrue(Files.notExists(tempFile));341assertTrue(Files.exists(cut));342assertEquals(Files.readString(cut), contents);343}344345/**346* When the substituted drive is a symlink, test if it has the same347* attributes as its target.348* Note: requires administrator privileges.349*/350@Test351public void testMoveAndCopyFilesToSymlinkedDrive() throws IOException {352if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {353return;354}355356Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");357Path tempLink = Path.of(tempDirectory.toString() + "_link");358Files.createSymbolicLink(tempLink, tempDirectory);359360substCreate(SUBST_DRIVE, tempLink);361362assertEquals(363Files.readAttributes(SUBST_DRIVE, "*"),364Files.readAttributes(tempDirectory, "*"));365366assertTrue(Files.isWritable(SUBST_DRIVE));367}368369/**370* Run a command and optionally prints stdout contents to371* `customOutputStream`.372*/373private void runCmd(ProcessBuilder pb, PrintStream customOutputStream) {374try {375PrintStream ps = customOutputStream != null ?376customOutputStream :377System.out;378OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(pb)379.outputTo(ps)380.errorTo(System.err);381382int exitCode = outputAnalyzer.getExitValue();383assertEquals(384exitCode /* actual value */,3850 /* expected value */,386String.format(387"Command `%s` failed with exit code %d",388pb.command(),389exitCode390)391);392393} catch (Throwable t) {394throw new RuntimeException(t);395}396}397398/**399* Helper to map a path to a drive letter using subst.400* For reference, see:401* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst402*/403private void substCreate(Path drive, Path path) {404runCmd(405new ProcessBuilder(406"cmd", "/c", "subst", drive.toString(), path.toString()),407null /* customOutputStream */);408}409410/**411* Delete a drive mapping using subst.412* For reference, see:413* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst414*/415private void substDelete(Path drive) throws IOException {416runCmd(417new ProcessBuilder(418"cmd", "/c", "subst", drive.toString(), "/D"),419null /* customOutputStream */);420}421422/**423* Return a list of strings that represents all the currently mapped drives.424* For instance, with the following output of subst:425* A:\: => path1426* B:\: => path2427* T:\: => path3428* X:\: => path4429* The function returns: ["A:\", "B:\", "T:\", "X:\"]430* For reference, see:431* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst432*/433private Stream<String> substFindMappedDrives() throws UnsupportedEncodingException {434ByteArrayOutputStream baos = new ByteArrayOutputStream();435String utf8 = StandardCharsets.UTF_8.name();436try (PrintStream ps = new PrintStream(baos, true, utf8)) {437// subst without any arguments returns a list of drives that438// are being substituted439runCmd(new ProcessBuilder("cmd", "/c", "subst"), ps);440String stdout = baos.toString(utf8);441return stdout442// split lines443.lines()444// only examine lines with "=>"445.filter(line -> line.contains("=>"))446// split each line into 2 components and take the first one447.map(line -> line.split("=>")[0].trim());448}449}450451/**452* subst can fail if the drive to be mapped already exists. The method returns453* a drive that is available.454*/455private Optional<Path> findAvailableDrive(Path tempDirectory) {456for (char letter = 'Z'; letter >= 'A'; letter--) {457try {458Path p = Path.of(letter + ":");459substCreate(p, tempDirectory);460substDelete(p);461return Optional.of(p);462} catch (Throwable t) {463// fall through464}465}466return Optional.empty();467}468}469470471