/* * 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(); } }