diff --git a/core/pom.xml b/core/pom.xml index caaf149fb..cf0fc9a0f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -154,6 +154,12 @@ truth test + + com.google.jimfs + jimfs + 1.3.2 + test + org.mockito mockito-core diff --git a/core/src/main/java/com/google/adk/skills/LocalSkillSource.java b/core/src/main/java/com/google/adk/skills/LocalSkillSource.java index 8cb465521..8ed2a91be 100644 --- a/core/src/main/java/com/google/adk/skills/LocalSkillSource.java +++ b/core/src/main/java/com/google/adk/skills/LocalSkillSource.java @@ -23,6 +23,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.nio.file.Files.isDirectory; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Maybe; @@ -37,6 +38,8 @@ /** Loads skills from the local file system. */ public final class LocalSkillSource extends AbstractSkillSource { + private static final Joiner PATH_JOINER = Joiner.on('/'); + private final Path skillsBasePath; public LocalSkillSource(Path skillsBasePath) { @@ -71,7 +74,9 @@ public Single> listResources(String skillName, String reso return paths .filter(Files::isRegularFile) .map(skillDir::relativize) - .map(Path::toString) + // Join the path's name elements with '/' on every OS, not the platform + // separator, to match ClassPathSkillSource and InMemorySkillSource. + .map(relativePath -> PATH_JOINER.join(relativePath)) .collect(toImmutableList()); } }) diff --git a/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java b/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java index d66cfe283..5d6a629cd 100644 --- a/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java +++ b/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java @@ -23,7 +23,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteSource; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; import java.io.IOException; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import org.junit.Rule; @@ -96,6 +99,27 @@ public void testListResources() throws IOException { assertThat(resources).containsExactly("assets/file1.txt", "assets/subdir/file2.txt"); } + @Test + public void testListResources_windowsFileSystem() throws IOException { + // Use Windows paths on any host so Ubuntu CI covers separator normalization. + try (FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows())) { + Path skillsBase = fileSystem.getPath("C:\\skills"); + Path assetsDir = skillsBase.resolve("my-skill").resolve("assets"); + Files.createDirectories(assetsDir.resolve("subdir")); + Files.writeString(assetsDir.resolve("file1.txt"), "resource content"); + Files.writeString(assetsDir.resolve("subdir").resolve("file2.txt"), "resource content"); + + SkillSource source = new LocalSkillSource(skillsBase); + ImmutableList resources = source.listResources("my-skill", "assets").blockingGet(); + + assertThat(resources).containsExactly("assets/file1.txt", "assets/subdir/file2.txt"); + for (String resourcePath : resources) { + ByteSource resource = source.loadResource("my-skill", resourcePath).blockingGet(); + assertThat(new String(resource.read(), UTF_8)).isEqualTo("resource content"); + } + } + } + @Test public void testListResources_notDirectory() throws IOException { Path skillsBase = tempFolder.getRoot().toPath().resolve("skills");