From 1cc1e54245a7174fb5bcc439578ec2289f5f2206 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Tue, 21 Dec 2021 13:00:10 +0100 Subject: [PATCH] LUCENE-10330: Make the mmap directory tests fail by default, if unmapping does not work (#556) Co-authored-by: Dawid Weiss --- lucene/core.tests/build.gradle | 28 +++++++++++++++ lucene/core.tests/src/test/module-info.java | 25 +++++++++++++ .../apache/lucene/core/tests/TestMMap.java | 35 +++++++++++++++++++ .../apache/lucene/store/MMapDirectory.java | 20 +++++------ .../lucene/store/TestMmapDirectory.java | 8 ++--- .../apache/lucene/store/TestMultiMMap.java | 8 ++--- settings.gradle | 1 + 7 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 lucene/core.tests/build.gradle create mode 100644 lucene/core.tests/src/test/module-info.java create mode 100644 lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java diff --git a/lucene/core.tests/build.gradle b/lucene/core.tests/build.gradle new file mode 100644 index 00000000000..9ada3003aaa --- /dev/null +++ b/lucene/core.tests/build.gradle @@ -0,0 +1,28 @@ +/* + * 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. + */ + +apply plugin: 'java-library' + +description = 'Module tests for :lucene:core' + +dependencies { + moduleTestImplementation project(':lucene:core') + moduleTestImplementation("junit:junit", { + exclude group: "org.hamcrest" + }) + moduleTestImplementation "org.hamcrest:hamcrest" +} diff --git a/lucene/core.tests/src/test/module-info.java b/lucene/core.tests/src/test/module-info.java new file mode 100644 index 00000000000..9b3cf5a07f0 --- /dev/null +++ b/lucene/core.tests/src/test/module-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** Test module for {@code org.apache.lucene.core}. */ +@SuppressWarnings({"requires-automatic"}) +module org.apache.lucene.core.tests { + requires org.apache.lucene.core; + requires junit; + + exports org.apache.lucene.core.tests; +} diff --git a/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java b/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java new file mode 100644 index 00000000000..23248465893 --- /dev/null +++ b/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java @@ -0,0 +1,35 @@ +/* + * 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.lucene.core.tests; + +import org.apache.lucene.store.MMapDirectory; +import org.junit.Assert; +import org.junit.Test; + +public class TestMMap { + @Test + public void testUnmapSupported() throws Exception { + final Module module = MMapDirectory.class.getModule(); + Assert.assertTrue("Lucene Core is not loaded as module", module.isNamed()); + Assert.assertTrue( + "Lucene Core can't read 'jdk.unsupported' module", + module.getLayer().findModule("jdk.unsupported").map(module::canRead).orElse(false)); + + // check that MMapDirectory can unmap by running the autodetection logic: + Assert.assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); + } +} diff --git a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java index 161cc384046..6e5b1813a97 100644 --- a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java +++ b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java @@ -346,9 +346,7 @@ public class MMapDirectory extends FSDirectory { } } - @SuppressForbidden( - reason = - "Needs access to private APIs in DirectBuffer, sun.misc.Cleaner, and sun.misc.Unsafe to enable hack") + @SuppressForbidden(reason = "Needs access to sun.misc.Unsafe to enable hack") private static Object unmapHackImpl() { final Lookup lookup = lookup(); try { @@ -363,29 +361,31 @@ public class MMapDirectory extends FSDirectory { final Field f = unsafeClass.getDeclaredField("theUnsafe"); f.setAccessible(true); final Object theUnsafe = f.get(null); - return newBufferCleaner(ByteBuffer.class, unmapper.bindTo(theUnsafe)); + return newBufferCleaner(unmapper.bindTo(theUnsafe)); } catch (SecurityException se) { return "Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: " + se + " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\") " + " and ReflectPermission(\"suppressAccessChecks\")]"; } catch (ReflectiveOperationException | RuntimeException e) { + final Module module = MMapDirectory.class.getModule(); + final ModuleLayer layer = module.getLayer(); + // classpath / unnamed module has no layer, so we need to check: + if (layer != null + && layer.findModule("jdk.unsupported").map(module::canRead).orElse(false) == false) { + return "Unmapping is not supported, because Lucene cannot read 'jdk.unsupported' module."; + } return "Unmapping is not supported on this platform, because internal Java APIs are not compatible with this Lucene version: " + e; } } - private static BufferCleaner newBufferCleaner( - final Class unmappableBufferClass, final MethodHandle unmapper) { + private static BufferCleaner newBufferCleaner(final MethodHandle unmapper) { assert Objects.equals(methodType(void.class, ByteBuffer.class), unmapper.type()); return (String resourceDescription, ByteBuffer buffer) -> { if (!buffer.isDirect()) { throw new IllegalArgumentException("unmapping only works with direct buffers"); } - if (!unmappableBufferClass.isInstance(buffer)) { - throw new IllegalArgumentException( - "buffer is not an instance of " + unmappableBufferClass.getName()); - } final Throwable error = AccessController.doPrivileged( (PrivilegedAction) diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java index 1ae552b7bce..46c59346abb 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Random; import java.util.concurrent.CountDownLatch; +import org.junit.BeforeClass; import org.junit.Ignore; /** Tests MMapDirectory */ @@ -34,10 +35,9 @@ public class TestMmapDirectory extends BaseDirectoryTestCase { return m; } - @Override - public void setUp() throws Exception { - super.setUp(); - assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); + @BeforeClass + public static void beforeClass() throws Exception { + assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); } @Ignore( diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java index b5d22e8ac94..317e4a8334d 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java @@ -19,6 +19,7 @@ package org.apache.lucene.store; import java.io.IOException; import java.nio.file.Path; import org.apache.lucene.util.BytesRef; +import org.junit.BeforeClass; /** * Tests MMapDirectory's MultiMMapIndexInput @@ -33,10 +34,9 @@ public class TestMultiMMap extends BaseChunkedDirectoryTestCase { return new MMapDirectory(path, maxChunkSize); } - @Override - public void setUp() throws Exception { - super.setUp(); - assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); + @BeforeClass + public static void beforeClass() throws Exception { + assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); } // TODO: can we improve ByteBuffersDirectory (without overhead) and move these clone safety tests diff --git a/settings.gradle b/settings.gradle index 2d48e4194dc..ed641bf1a6b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -41,6 +41,7 @@ include "lucene:benchmark" include "lucene:classification" include "lucene:codecs" include "lucene:core" +include "lucene:core.tests" include "lucene:demo" include "lucene:distribution" include "lucene:distribution.tests"