--- /dev/null 2016-04-05 09:31:53.373418148 +0200 +++ new/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java 2016-04-08 10:43:28.638665905 +0200 @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.reflect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Functional and concurrency test for ClassLoaderValue + * + * @author Peter Levart + */ +public class ClassLoaderValueTest { + + @SuppressWarnings("unchecked") + public static void main(String[] args) throws Exception { + + ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), + new ClassLoaderValue<>()}; + + ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), + ClassLoader.getPlatformClassLoader(), + null /* bootstrap class loader */}; + + Integer[] keys = new Integer[32]; + for (int i = 0; i < keys.length; i++) { + keys[i] = i + 128; + } + + try (AutoCloseable cleanup = () -> { + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + clv.removeAll(ld); + } + } + }) { + // 1st just one sequential pass of single-threaded validation + // which is easier to debug if it fails... + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + writeValidateOps(clv, ld, keys); + } + } + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + readValidateOps(clv, ld, keys); + } + } + + // 2nd the same in concurrent setting that also validates + // failure-isolation between threads and data-isolation between + // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage + testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); + } + } + + static void writeValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer pv; + boolean success; + + pv = clv.sub(k).putIfAbsent(ld, v1); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v1); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, v1); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v2); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + try { + // nested get for same key should throw + clv_k.get(_ld); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + try { + // nested putIfAbsent for same key should throw + clv_k.putIfAbsent(_ld, v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // nested remove for for same key and any value (even null) + // should return false + assertEquals(clv_k.remove(_ld, null), false); + assertEquals(clv_k.remove(_ld, v1), false); + assertEquals(clv_k.remove(_ld, v2), false); + try { + // nested computeIfAbsent for same key should throw + clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // if everything above has been handled, we should succeed... + return v2; + }); + // ... and the result should be reflected in the CLV + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + try { + clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + throw new UnsupportedOperationException(); + }); + throw new AssertionError("Unexpected code path"); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(clv.sub(k).get(ld), null); + } + } + + static void readValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer rv = clv.sub(k).get(ld); + if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { + throw new AssertionError("Unexpected value: " + rv + + ", expected one of: null, " + v1 + ", " + v2); + } + } + } + + static void testConcurrentIsolation(ClassLoaderValue[] clvs, + ClassLoader[] lds, + Object[] keys, + long millisRuntime) { + ExecutorService exe = Executors.newCachedThreadPool(); + List> futures = new ArrayList<>(); + AtomicBoolean stop = new AtomicBoolean(); + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + // submit a task that exercises a mix of modifying + // and reading-validating operations in an isolated + // part of the storage. If isolation is violated, + // validation operations are expected to fail. + futures.add(exe.submit(() -> { + do { + writeValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + // submit a task that just reads from the same part of + // the storage as above task. It should not disturb + // above task in any way and this task should never + // exhibit any failure although above task produces + // regular failures during lazy computation + futures.add(exe.submit(() -> { + do { + readValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + } + } + // wait for some time + try { + Thread.sleep(millisRuntime); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + // stop tasks + stop.set(true); + // collect results + AssertionError error = null; + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + if (error == null) error = new AssertionError("Failure"); + error.addSuppressed(e); + } + } + exe.shutdown(); + if (error != null) throw error; + } + + static void assertEquals(Object actual, Object expected) { + if (!Objects.equals(actual, expected)) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +}