--- /dev/null 2017-02-06 22:16:02.409658130 -0500 +++ new/test/lib/jdk/test/lib/gc/TestConcurrentPhaseControlSupport.java 2017-02-17 20:03:46.283690301 -0500 @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2017, 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. + */ + +// Support library for TestConcurrentPhaseControl variants. +// +// To use: +// +// (1) Derive a class from TestConcurrentPhaseControlSupport, and +// derive a class from TestConcurrentPhaseControlSupport.Executor, +// providing definitions for the abstract methods. +// +// (2) The Executor subclass must provide a public static main +// function which constructs an instance of that subclass and calls +// its run() method. +// +// (3) The test subclass provides a public static main function which +// constructs an instance of that subclass and calls its run() method. +// +// (4) The test program must provide access to WhiteBox, as it is used +// by this support class. +// +// (5) The test program should be invoked as a driver. The support +// class's run() function will run the Executor subclass in a +// subprocess, in order to capture it's output for analysis. + +package jdk.test.lib.gc; + +import sun.hotspot.WhiteBox; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public abstract class TestConcurrentPhaseControlSupport { + // Return an array of pairs of strings. Each pair is a phase name + // and a regex pattern for recognizing the associated log message. + // The regex pattern can be null if no log message is associated + // with the named phase. The test will iterate through the array, + // requesting each phase in turn. + protected abstract String[][] gcPhases(); + + // Command line options for invoking the desired collector and + // logging options to produce output that can be matched against + // the regex patterns in the gcPhases() pairs. + protected abstract String[] gcOptions(); + + // Name of the class that will execute the control requests. It + // should be a subclass of our Executor class below. + protected abstract String executorClassName(); + + // The name of the GC, logged as "Using " near the beginning + // of the log output. + protected abstract String gcName(); + + private static final String requestPrefix = "Requesting concurrent phase: "; + private static final String reachedPrefix = "Reached concurrent phase: "; + + private static void fail(String message) throws Exception { + throw new RuntimeException(message); + } + + public String executeTest() throws Exception { + System.out.println("\n---------- Testing ---------"); + + final String[] wb_arguments = { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI" + }; + + List arglist = new ArrayList(); + Collections.addAll(arglist, wb_arguments); + Collections.addAll(arglist, gcOptions()); + Collections.addAll(arglist, executorClassName()); + String[] arguments = arglist.toArray(new String[arglist.size()]); + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + + String messages = output.getStdout(); + System.out.println(messages); + + output.shouldHaveExitValue(0); + output.shouldContain("Using " + gcName()); + + return messages; + } + + public void checkPhaseControl(String messages) throws Exception { + // Iterate through the phase sequence for the test, verifying + // output contains appropriate sequences of request message, + // log message for phase, and request reached message. Note + // that a log message for a phase may occur later than the + // associated request reached message, or even the following + // request message. + + Pattern nextReqP = Pattern.compile(requestPrefix); + Matcher nextReqM = nextReqP.matcher(messages); + + Pattern nextReachP = Pattern.compile(reachedPrefix); + Matcher nextReachM = nextReachP.matcher(messages); + + String pendingPhaseMessage = null; + int pendingPhaseMessagePosition = -1; + + int position = 0; + for (String[] phase: gcPhases()) { + String phaseName = phase[0]; + String phaseMsg = phase[1]; + + System.out.println("Processing phase " + phaseName); + + // Update the "next" matchers to refer to the next + // corresponding pair of request and reached messages. + if (!nextReqM.find()) { + fail("Didn't find next phase request"); + } else if ((position != 0) && (nextReqM.start() < nextReachM.end())) { + fail("Next request before previous reached"); + } else if (!nextReachM.find()) { + fail("Didn't find next phase reached"); + } else if (nextReachM.start() <= nextReqM.end()) { + fail("Next request/reached misordered"); + } + + // Find the expected request message, and ensure it is the next. + Pattern reqP = Pattern.compile(requestPrefix + phaseName); + Matcher reqM = reqP.matcher(messages); + if (!reqM.find(position)) { + fail("Didn't find request for " + phaseName); + } else if (reqM.start() != nextReqM.start()) { + fail("Request mis-positioned for " + phaseName); + } + + // Find the expected reached message, and ensure it is the next. + Pattern reachP = Pattern.compile(reachedPrefix + phaseName); + Matcher reachM = reachP.matcher(messages); + if (!reachM.find(position)) { + fail("Didn't find reached for " + phaseName); + } else if (reachM.start() != nextReachM.start()) { + fail("Reached mis-positioned for " + phaseName); + } + + // If there is a pending log message (see below), ensure + // it was before the current reached message. + if (pendingPhaseMessage != null) { + if (pendingPhaseMessagePosition >= reachM.start()) { + fail("Log message after next reached message: " + + pendingPhaseMessage); + } + } + + // If the phase has an associated logging message, verify + // such a logging message is present following the + // request, and otherwise positioned appropriately. The + // complication here is that the logging message + // associated with a request might follow the reached + // message, and even the next request message, if there is + // a later request. But it must preceed the next + // logging message and the next reached message. + boolean clearPendingPhaseMessage = true; + if (phaseMsg != null) { + Pattern logP = Pattern.compile("GC\\(\\d+\\)\\s+" + phaseMsg); + Matcher logM = logP.matcher(messages); + if (!logM.find(reqM.end())) { + fail("Didn't find message " + phaseMsg); + } + + if (pendingPhaseMessage != null) { + if (pendingPhaseMessagePosition >= logM.start()) { + fail("Log messages out of order: " + + pendingPhaseMessage + " should preceed " + + phaseMsg); + } + } + + if (reachM.end() <= logM.start()) { + clearPendingPhaseMessage = false; + pendingPhaseMessage = phaseMsg; + pendingPhaseMessagePosition = logM.end(); + } + } + if (clearPendingPhaseMessage) { + pendingPhaseMessage = null; + pendingPhaseMessagePosition = -1; + } + + // Update position for start of next phase search. + position = reachM.end(); + } + // It's okay for there to be a leftover pending phase message. + // We know it was found before the end of the log. + } + + public void run() throws Exception { + String messages = executeTest(); + checkPhaseControl(messages); + } + + public static abstract class Executor { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + private void step(String phase) { + System.out.println(requestPrefix + phase); + WB.requestConcurrentGCPhase(phase); + System.out.println(reachedPrefix + phase); + } + + public void run() throws Exception { + // Iterate through test sequence of phases, requesting each. + for (String[] phaseData: gcPhases()) { + step(phaseData[0]); + } + // Wait a little to allow a delayed logging message for + // the final request/reached to be printed before exiting + // the program. + Thread.sleep(250); + } + + // Return the same array of string pairs as returned by the + // test support's method. + protected abstract String[][] gcPhases(); + } +}