diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java index 0ca3a3a3943f54..487fe3404f1545 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java @@ -15,7 +15,6 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; -import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -46,7 +45,6 @@ import com.google.devtools.build.lib.vfs.Symlinks; import com.google.devtools.build.lib.vfs.SyscallCache; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -58,7 +56,7 @@ /** Tests for {@link RemoteActionFileSystem} */ @RunWith(JUnit4.class) -public final class RemoteActionFileSystemTest { +public final class RemoteActionFileSystemTest extends RemoteActionFileSystemTestBase { private static final DigestHashFunction HASH_FUNCTION = DigestHashFunction.SHA256; @@ -70,17 +68,48 @@ public final class RemoteActionFileSystemTest { ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "out"); @Before - public void createOutputRoot() throws IOException { + public void setUp() throws IOException { outputRoot.getRoot().asPath().createDirectoryAndParents(); } + @Override + protected RemoteActionFileSystem createActionFileSystem( + ActionInputMap inputs, Iterable outputs) throws IOException { + RemoteActionFileSystem remoteActionFileSystem = + new RemoteActionFileSystem( + fs, + execRoot.asFragment(), + outputRoot.getRoot().asPath().relativeTo(execRoot).getPathString(), + inputs, + outputs, + inputFetcher); + remoteActionFileSystem.updateContext(metadataInjector); + remoteActionFileSystem.createDirectoryAndParents(outputRoot.getRoot().asPath().asFragment()); + return remoteActionFileSystem; + } + + @Override + protected FileSystem getLocalFileSystem(FileSystem actionFs) { + return ((RemoteActionFileSystem) actionFs).getLocalFileSystem(); + } + + @Override + protected FileSystem getRemoteFileSystem(FileSystem actionFs) { + return ((RemoteActionFileSystem) actionFs).getRemoteOutputTree(); + } + + @Override + protected PathFragment getOutputPath(String outputRootRelativePath) { + return outputRoot.getRoot().asPath().getRelative(outputRootRelativePath).asFragment(); + } + @Test public void testGetInputStream() throws Exception { // arrange ActionInputMap inputs = new ActionInputMap(2); Artifact remoteArtifact = createRemoteArtifact("remote-file", "remote contents", inputs); Artifact localArtifact = createLocalArtifact("local-file", "local contents", inputs); - FileSystem actionFs = newRemoteActionFileSystem(inputs); + FileSystem actionFs = createActionFileSystem(inputs); doAnswer( invocationOnMock -> { FileSystemUtils.writeContent( @@ -114,7 +143,7 @@ public void createSymbolicLink_localFileArtifact() throws IOException { Artifact localArtifact = createLocalArtifact("local-file", "local contents", inputs); Artifact outputArtifact = ActionsTestUtil.createArtifact(outputRoot, "out"); ImmutableList outputs = ImmutableList.of(outputArtifact); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs, outputs); + RemoteActionFileSystem actionFs = createActionFileSystem(inputs, outputs); // act PathFragment linkPath = outputArtifact.getPath().asFragment(); @@ -144,7 +173,7 @@ public void createSymbolicLink_remoteFileArtifact() throws IOException { Artifact remoteArtifact = createRemoteArtifact("remote-file", "remote contents", inputs); Artifact outputArtifact = ActionsTestUtil.createArtifact(outputRoot, "out"); ImmutableList outputs = ImmutableList.of(outputArtifact); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs, outputs); + RemoteActionFileSystem actionFs = createActionFileSystem(inputs, outputs); // act PathFragment linkPath = outputArtifact.getPath().asFragment(); @@ -184,7 +213,7 @@ public void createSymbolicLink_localTreeArtifact() throws IOException { SpecialArtifact outputArtifact = ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputRoot, "out"); ImmutableList outputs = ImmutableList.of(outputArtifact); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs, outputs); + RemoteActionFileSystem actionFs = createActionFileSystem(inputs, outputs); // act PathFragment linkPath = outputArtifact.getPath().asFragment(); @@ -217,7 +246,7 @@ public void createSymbolicLink_remoteTreeArtifact() throws IOException { SpecialArtifact outputArtifact = ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputRoot, "out"); ImmutableList outputs = ImmutableList.of(outputArtifact); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs, outputs); + RemoteActionFileSystem actionFs = createActionFileSystem(inputs, outputs); // act PathFragment linkPath = outputArtifact.getPath().asFragment(); @@ -252,7 +281,7 @@ public void createSymbolicLink_unresolvedSymlink() throws IOException { SpecialArtifact outputArtifact = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputRoot, "out"); ImmutableList outputs = ImmutableList.of(outputArtifact); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs, outputs); + RemoteActionFileSystem actionFs = createActionFileSystem(inputs, outputs); PathFragment targetPath = PathFragment.create("some/path"); // act @@ -275,222 +304,36 @@ public void createSymbolicLink_unresolvedSymlink() throws IOException { verifyNoInteractions(metadataInjector); } - @Test - public void exists_fileDoesNotExist_returnsFalse() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - - assertThat(actionFs.exists(path)).isFalse(); - } - - @Test - public void exists_localFile_returnsTrue() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - - writeLocalFile(actionFs, path, "local contents"); - - assertThat(actionFs.exists(path)).isTrue(); - } - - @Test - public void exists_remoteFile_returnsTrue() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - - injectRemoteFile(actionFs, path, "remote contents"); - - assertThat(actionFs.exists(path)).isTrue(); - } - - @Test - public void exists_localAndRemoteFile_returnsTrue() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - - writeLocalFile(actionFs, path, "local contents"); - injectRemoteFile(actionFs, path, "remote contents"); - - assertThat(actionFs.exists(path)).isTrue(); - } - - @Test - public void delete_fileDoesNotExist_returnsFalse() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - - assertThat(actionFs.delete(path)).isFalse(); - } - - @Test - public void delete_localFile_succeeds() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - writeLocalFile(actionFs, path, "local contents"); - assertThat(actionFs.getLocalFileSystem().exists(path)).isTrue(); - - boolean success = actionFs.delete(path); - - assertThat(success).isTrue(); - assertThat(actionFs.getLocalFileSystem().exists(path)).isFalse(); - } - - @Test - public void delete_remoteFile_succeeds() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - injectRemoteFile(actionFs, path, "remote contents"); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isTrue(); - - boolean success = actionFs.delete(path); - - assertThat(success).isTrue(); - assertThat(actionFs.exists(path)).isFalse(); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isFalse(); - } - - @Test - public void delete_localAndRemoteFile_succeeds() throws Exception { - ActionInputMap inputs = new ActionInputMap(0); - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(inputs); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - writeLocalFile(actionFs, path, "local contents"); - injectRemoteFile(actionFs, path, "remote contents"); - assertThat(actionFs.getLocalFileSystem().exists(path)).isTrue(); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isTrue(); - - boolean success = actionFs.delete(path); - - assertThat(success).isTrue(); - assertThat(actionFs.exists(path)).isFalse(); - assertThat(actionFs.getLocalFileSystem().exists(path)).isFalse(); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isFalse(); - } - - @Test - public void renameTo_fileDoesNotExist_throwError() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - PathFragment newPath = outputRoot.getRoot().asPath().getRelative("file-new").asFragment(); - - assertThrows(FileNotFoundException.class, () -> actionFs.renameTo(path, newPath)); - } - - @Test - public void renameTo_onlyRemoteFile_renameRemoteFile() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - injectRemoteFile(actionFs, path, "remote-content"); - PathFragment newPath = outputRoot.getRoot().asPath().getRelative("file-new").asFragment(); - - actionFs.renameTo(path, newPath); - - assertThat(actionFs.exists(path)).isFalse(); - assertThat(actionFs.exists(newPath)).isTrue(); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isFalse(); - assertThat(actionFs.getRemoteOutputTree().exists(newPath)).isTrue(); - } - @Test public void renameTo_onlyLocalFile_renameLocalFile() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); writeLocalFile(actionFs, path, "local-content"); - PathFragment newPath = outputRoot.getRoot().asPath().getRelative("file-new").asFragment(); + PathFragment newPath = getOutputPath("file-new"); actionFs.renameTo(path, newPath); assertThat(actionFs.exists(path)).isFalse(); assertThat(actionFs.exists(newPath)).isTrue(); - assertThat(actionFs.getLocalFileSystem().exists(path)).isFalse(); - assertThat(actionFs.getLocalFileSystem().exists(newPath)).isTrue(); - } - - @Test - public void renameTo_localAndRemoteFile_renameBoth() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - PathFragment path = outputRoot.getRoot().asPath().getRelative("file").asFragment(); - injectRemoteFile(actionFs, path, "remote-content"); - writeLocalFile(actionFs, path, "local-content"); - PathFragment newPath = outputRoot.getRoot().asPath().getRelative("file-new").asFragment(); - - actionFs.renameTo(path, newPath); - - assertThat(actionFs.exists(path)).isFalse(); - assertThat(actionFs.exists(newPath)).isTrue(); - assertThat(actionFs.getRemoteOutputTree().exists(path)).isFalse(); - assertThat(actionFs.getRemoteOutputTree().exists(newPath)).isTrue(); - assertThat(actionFs.getLocalFileSystem().exists(path)).isFalse(); - assertThat(actionFs.getLocalFileSystem().exists(newPath)).isTrue(); - } - - @Test - public void createDirectoryAndParents_createLocallyAndRemotely() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - PathFragment path = outputRoot.getRoot().asPath().getRelative("dir").asFragment(); - - actionFs.createDirectoryAndParents(path); - - assertThat(actionFs.getRemoteOutputTree().getPath(path).isDirectory()).isTrue(); - assertThat(actionFs.getLocalFileSystem().getPath(path).isDirectory()).isTrue(); - } - - @Test - public void createDirectory_createLocallyAndRemotely() throws Exception { - RemoteActionFileSystem actionFs = newRemoteActionFileSystem(); - actionFs.createDirectoryAndParents(outputRoot.getRoot().asPath().asFragment()); - PathFragment path = outputRoot.getRoot().asPath().getRelative("dir").asFragment(); - - actionFs.createDirectory(path); - - assertThat(actionFs.getRemoteOutputTree().getPath(path).isDirectory()).isTrue(); - assertThat(actionFs.getLocalFileSystem().getPath(path).isDirectory()).isTrue(); + assertThat(getLocalFileSystem(actionFs).exists(path)).isFalse(); + assertThat(getLocalFileSystem(actionFs).exists(newPath)).isTrue(); } - private void injectRemoteFile(RemoteActionFileSystem actionFs, PathFragment path, String content) + @Override + protected void injectRemoteFile(FileSystem actionFs, PathFragment path, String content) throws IOException { byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8); HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contentBytes); - actionFs.injectRemoteFile(path, hashCode.asBytes(), contentBytes.length, "action-id"); + ((RemoteActionFileSystem) actionFs) + .injectRemoteFile(path, hashCode.asBytes(), contentBytes.length, "action-id"); } - private void writeLocalFile(RemoteActionFileSystem actionFs, PathFragment path, String content) + @Override + protected void writeLocalFile(FileSystem actionFs, PathFragment path, String content) throws IOException { FileSystemUtils.writeContent(actionFs.getPath(path), StandardCharsets.UTF_8, content); } - private RemoteActionFileSystem newRemoteActionFileSystem() throws IOException { - ActionInputMap inputs = new ActionInputMap(0); - return newRemoteActionFileSystem(inputs, ImmutableList.of()); - } - - private RemoteActionFileSystem newRemoteActionFileSystem(ActionInputMap inputs) - throws IOException { - return newRemoteActionFileSystem(inputs, ImmutableList.of()); - } - - private RemoteActionFileSystem newRemoteActionFileSystem( - ActionInputMap inputs, Iterable outputs) throws IOException { - RemoteActionFileSystem remoteActionFileSystem = - new RemoteActionFileSystem( - fs, - execRoot.asFragment(), - outputRoot.getRoot().asPath().relativeTo(execRoot).getPathString(), - inputs, - outputs, - inputFetcher); - remoteActionFileSystem.updateContext(metadataInjector); - remoteActionFileSystem.createDirectoryAndParents(outputRoot.getRoot().asPath().asFragment()); - return remoteActionFileSystem; - } - /** Returns a remote artifact and puts its metadata into the action input map. */ private Artifact createRemoteArtifact( String pathFragment, String contents, ActionInputMap inputs) { diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTestBase.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTestBase.java new file mode 100644 index 00000000000000..fd8a82d80cda6c --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTestBase.java @@ -0,0 +1,212 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed 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 com.google.devtools.build.lib.remote; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.ActionInputMap; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.vfs.FileSystem; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.io.FileNotFoundException; +import java.io.IOException; +import org.junit.Test; + +public abstract class RemoteActionFileSystemTestBase { + protected abstract FileSystem createActionFileSystem( + ActionInputMap inputs, Iterable outputs) throws IOException; + + protected FileSystem createActionFileSystem() throws IOException { + ActionInputMap inputs = new ActionInputMap(0); + return createActionFileSystem(inputs); + } + + protected FileSystem createActionFileSystem(ActionInputMap inputs) throws IOException { + return createActionFileSystem(inputs, ImmutableList.of()); + } + + protected abstract FileSystem getLocalFileSystem(FileSystem actionFs); + + protected abstract FileSystem getRemoteFileSystem(FileSystem actionFs); + + protected abstract PathFragment getOutputPath(String outputRootRelativePath); + + protected abstract void writeLocalFile(FileSystem actionFs, PathFragment path, String content) + throws IOException; + + protected abstract void injectRemoteFile(FileSystem actionFs, PathFragment path, String content) + throws IOException; + + @Test + public void exists_fileDoesNotExist_returnsFalse() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + + boolean exists = actionFs.exists(path); + + assertThat(exists).isFalse(); + } + + @Test + public void exists_localFile_returnsTrue() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + + writeLocalFile(actionFs, path, "local contents"); + + assertThat(actionFs.exists(path)).isTrue(); + } + + @Test + public void exists_remoteFile_returnsTrue() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + + injectRemoteFile(actionFs, path, "remote contents"); + + assertThat(actionFs.exists(path)).isTrue(); + } + + @Test + public void exists_localAndRemoteFile_returnsTrue() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + + writeLocalFile(actionFs, path, "local contents"); + injectRemoteFile(actionFs, path, "remote contents"); + + assertThat(actionFs.exists(path)).isTrue(); + } + + @Test + public void delete_fileDoesNotExist_returnsFalse() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + + boolean success = actionFs.getPath(path).delete(); + + assertThat(success).isFalse(); + } + + @Test + public void delete_localFile_succeeds() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + writeLocalFile(actionFs, path, "local contents"); + assertThat(getLocalFileSystem(actionFs).exists(path)).isTrue(); + + boolean success = actionFs.getPath(path).delete(); + + assertThat(success).isTrue(); + assertThat(getLocalFileSystem(actionFs).exists(path)).isFalse(); + } + + @Test + public void delete_remoteFile_succeeds() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + injectRemoteFile(actionFs, path, "remote contents"); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isTrue(); + + boolean success = actionFs.getPath(path).delete(); + + assertThat(success).isTrue(); + assertThat(actionFs.exists(path)).isFalse(); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isFalse(); + } + + @Test + public void delete_localAndRemoteFile_succeeds() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + writeLocalFile(actionFs, path, "local contents"); + injectRemoteFile(actionFs, path, "remote contents"); + assertThat(getLocalFileSystem(actionFs).exists(path)).isTrue(); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isTrue(); + + boolean success = actionFs.getPath(path).delete(); + + assertThat(success).isTrue(); + assertThat(actionFs.exists(path)).isFalse(); + assertThat(getLocalFileSystem(actionFs).exists(path)).isFalse(); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isFalse(); + } + + @Test + public void renameTo_fileDoesNotExist_throwError() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + PathFragment newPath = getOutputPath("file-new"); + + assertThrows(FileNotFoundException.class, () -> actionFs.renameTo(path, newPath)); + } + + @Test + public void renameTo_onlyRemoteFile_renameRemoteFile() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + injectRemoteFile(actionFs, path, "remote-content"); + PathFragment newPath = getOutputPath("file-new"); + + actionFs.renameTo(path, newPath); + + assertThat(actionFs.exists(path)).isFalse(); + assertThat(actionFs.exists(newPath)).isTrue(); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isFalse(); + assertThat(getRemoteFileSystem(actionFs).exists(newPath)).isTrue(); + } + + @Test + public void renameTo_localAndRemoteFile_renameBoth() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("file"); + injectRemoteFile(actionFs, path, "remote-content"); + writeLocalFile(actionFs, path, "local-content"); + PathFragment newPath = getOutputPath("file-new"); + + actionFs.renameTo(path, newPath); + + assertThat(actionFs.exists(path)).isFalse(); + assertThat(actionFs.exists(newPath)).isTrue(); + assertThat(getRemoteFileSystem(actionFs).exists(path)).isFalse(); + assertThat(getRemoteFileSystem(actionFs).exists(newPath)).isTrue(); + assertThat(getLocalFileSystem(actionFs).exists(path)).isFalse(); + assertThat(getLocalFileSystem(actionFs).exists(newPath)).isTrue(); + } + + @Test + public void createDirectoryAndParents_createLocallyAndRemotely() throws Exception { + FileSystem actionFs = createActionFileSystem(); + PathFragment path = getOutputPath("dir"); + + actionFs.createDirectoryAndParents(path); + + assertThat(getRemoteFileSystem(actionFs).getPath(path).isDirectory()).isTrue(); + assertThat(getLocalFileSystem(actionFs).getPath(path).isDirectory()).isTrue(); + } + + @Test + public void createDirectory_createLocallyAndRemotely() throws Exception { + FileSystem actionFs = createActionFileSystem(); + actionFs.createDirectoryAndParents(getOutputPath("parent")); + PathFragment path = getOutputPath("parent/dir"); + + actionFs.createDirectory(path); + + assertThat(getRemoteFileSystem(actionFs).getPath(path).isDirectory()).isTrue(); + assertThat(getLocalFileSystem(actionFs).getPath(path).isDirectory()).isTrue(); + } +}