This is an automated email from the ASF dual-hosted git repository. chesnay pushed a commit to branch release-1.16 in repository https://gitbox.apache.org/repos/asf/flink.git
commit c4d254a92a2346a5d29da9f41c4df0022d6c2f36 Author: Chesnay Schepler <[email protected]> AuthorDate: Tue Sep 13 18:26:15 2022 +0200 [FLINK-29287][tests] Add PackagingTestUtils --- .../apache/flink/packaging/PackagingTestUtils.java | 104 ++++++++++++++++ .../flink/packaging/PackagingTestUtilsTest.java | 134 +++++++++++++++++++++ 2 files changed, 238 insertions(+) diff --git a/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/packaging/PackagingTestUtils.java b/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/packaging/PackagingTestUtils.java new file mode 100644 index 00000000000..69cc2faa4ca --- /dev/null +++ b/flink-test-utils-parent/flink-test-utils/src/main/java/org/apache/flink/packaging/PackagingTestUtils.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package org.apache.flink.packaging; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** Test utils around jar packaging. */ +public class PackagingTestUtils { + + /** + * Verifies that all files in the jar match one of the provided allow strings. + * + * <p>An allow item ending on a {@code "/"} is treated as an allowed parent directory. + * Otherwise, it is treated as an allowed file. + * + * <p>For example, given a jar containing a file {@code META-INF/NOTICES}: + * + * <p>These would pass: + * + * <ul> + * <li>{@code "META-INF/"} + * <li>{@code "META-INF/NOTICES"} + * </ul> + * + * <p>These would fail: + * + * <ul> + * <li>{@code "META-INF"} + * <li>{@code "META-INF/NOTICE"} + * <li>{@code "META-INF/NOTICES/"} + * </ul> + */ + public static void assertJarContainsOnlyFilesMatching( + Path jarPath, Collection<String> allowedPaths) throws Exception { + final URI jar = jarPath.toUri(); + + try (final FileSystem fileSystem = + FileSystems.newFileSystem( + new URI("jar:file", jar.getHost(), jar.getPath(), jar.getFragment()), + Collections.emptyMap())) { + try (Stream<Path> walk = Files.walk(fileSystem.getPath("/"))) { + walk.filter(file -> !Files.isDirectory(file)) + .map(file -> file.toAbsolutePath().toString()) + .map(file -> file.startsWith("/") ? file.substring(1) : file) + .forEach( + file -> + assertThat(allowedPaths) + .as("Bad file in JAR: %s", file) + .anySatisfy( + allowedPath -> { + if (allowedPath.endsWith("/")) { + assertThat(file) + .startsWith(allowedPath); + } else { + assertThat(file) + .isEqualTo(allowedPath); + } + })); + } + } + } + + /** + * Verifies that the given jar contains a service entry file for the given service. + * + * <p>Caution: This only checks that the file exists; the content is not verified. + */ + public static void assertJarContainsServiceEntry(Path jarPath, Class<?> service) + throws Exception { + final URI jar = jarPath.toUri(); + + try (final FileSystem fileSystem = + FileSystems.newFileSystem( + new URI("jar:file", jar.getHost(), jar.getPath(), jar.getFragment()), + Collections.emptyMap())) { + assertThat(fileSystem.getPath("META-INF", "services", service.getName())).exists(); + } + } +} diff --git a/flink-test-utils-parent/flink-test-utils/src/test/java/org/apache/flink/packaging/PackagingTestUtilsTest.java b/flink-test-utils-parent/flink-test-utils/src/test/java/org/apache/flink/packaging/PackagingTestUtilsTest.java new file mode 100644 index 00000000000..451693c38dd --- /dev/null +++ b/flink-test-utils-parent/flink-test-utils/src/test/java/org/apache/flink/packaging/PackagingTestUtilsTest.java @@ -0,0 +1,134 @@ +/* + * 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. + */ + +package org.apache.flink.packaging; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PackagingTestUtilsTest { + + @Test + void testAssertJarContainsOnlyFilesMatching(@TempDir Path tmp) throws Exception { + Path jar = createJar(tmp, Entry.fileEntry("", Arrays.asList("META-INF", "NOTICES"))); + + PackagingTestUtils.assertJarContainsOnlyFilesMatching( + jar, Collections.singleton("META-INF/")); + PackagingTestUtils.assertJarContainsOnlyFilesMatching( + jar, Collections.singleton("META-INF/NOTICES")); + + assertThatThrownBy( + () -> + PackagingTestUtils.assertJarContainsOnlyFilesMatching( + jar, Collections.singleton("META-INF"))) + .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + PackagingTestUtils.assertJarContainsOnlyFilesMatching( + jar, Collections.singleton("META-INF/NOTICE"))) + .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + PackagingTestUtils.assertJarContainsOnlyFilesMatching( + jar, Collections.singleton("META-INF/NOTICES/"))) + .isInstanceOf(AssertionError.class); + } + + @Test + void testAssertJarContainsServiceEntry(@TempDir Path tmp) throws Exception { + final String service = PackagingTestUtilsTest.class.getName(); + Path jar = + createJar(tmp, Entry.fileEntry("", Arrays.asList("META-INF", "services", service))); + + PackagingTestUtils.assertJarContainsServiceEntry(jar, PackagingTestUtilsTest.class); + + assertThatThrownBy( + () -> + PackagingTestUtils.assertJarContainsServiceEntry( + jar, PackagingTestUtils.class)) + .isInstanceOf(AssertionError.class); + } + + private static class Entry { + final String contents; + final List<String> path; + final boolean isDirectory; + + public static Entry directoryEntry(List<String> path) { + return new Entry("", path, true); + } + + public static Entry fileEntry(String contents, List<String> path) { + return new Entry(contents, path, false); + } + + private Entry(String contents, List<String> path, boolean isDirectory) { + this.contents = contents; + this.path = path; + this.isDirectory = isDirectory; + } + } + + private static Path createJar(Path tempDir, Entry... entries) throws Exception { + final Path path = tempDir.resolve(UUID.randomUUID().toString() + ".jar"); + + final URI uri = path.toUri(); + + final URI jarUri = new URI("jar:file", uri.getHost(), uri.getPath(), uri.getFragment()); + + // causes FileSystems#newFileSystem to automatically create a valid zip file + // this is easier than manually creating a valid empty zip file manually + final Map<String, String> env = new HashMap<>(); + env.put("create", "true"); + + try (FileSystem zip = FileSystems.newFileSystem(jarUri, env)) { + // shortcut to getting the single root + final Path root = zip.getPath("/"); + for (Entry entry : entries) { + final Path zipPath = + root.resolve( + zip.getPath( + entry.path.get(0), + entry.path + .subList(1, entry.path.size()) + .toArray(new String[] {}))); + if (entry.isDirectory) { + Files.createDirectories(zipPath); + } else { + Files.createDirectories(zipPath.getParent()); + Files.write(zipPath, entry.contents.getBytes()); + } + } + } + return path; + } +}
