/* * Copyright (c) 2015, 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. */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.time.ZoneId; import java.util.Base64; import java.util.Locale; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.SimpleFormatter; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; /** * @test * @bug 8072645 * @summary tests the compatibility of LogRecord serial form between * JDK 8 and JDK 9. Ideally this test should be run on both platforms. * (It is designed to run on both). * @run main/othervm SerializeLogRecord * @author danielfuchs */ public class SerializeLogRecord { /** * Serializes a log record, encode the serialized bytes in base 64, and * prints pseudo java code that can be cut and pasted into this test. * @param record the log record to serialize, encode in base 64, and for * which test data will be generated. * @return A string containing the generated pseudo java code. * @throws IOException Unexpected. * @throws ClassNotFoundException Unexpected. */ public static String generate(LogRecord record) throws IOException, ClassNotFoundException { // Format the given logRecord using the SimpleFormatter SimpleFormatter formatter = new SimpleFormatter(); String str = formatter.format(record); // Serialize the given LogRecord final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(record); oos.flush(); oos.close(); // Now we're going to perform a number of smoke tests before // generating the Java pseudo code. // // First checks that the log record can be deserialized final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); final ObjectInputStream ois = new ObjectInputStream(bais); final LogRecord record2 = (LogRecord)ois.readObject(); // Format the deserialized LogRecord using the SimpleFormatter, and // check that the string representation obtained matches the string // representation of the original LogRecord String str2 = formatter.format(record2); if (!str.equals(str2)) throw new RuntimeException("Unexpected values in deserialized object:" + "\n\tExpected: " + str + "\n\tRetrieved: "+str); // Now get a Base64 string representation of the serialized bytes. final String base64 = Base64.getEncoder().encodeToString(baos.toByteArray()); // Check that we can deserialize a log record from the Base64 string // representation we just computed. final ByteArrayInputStream bais2 = new ByteArrayInputStream(Base64.getDecoder().decode(base64)); final ObjectInputStream ois2 = new ObjectInputStream(bais2); final LogRecord record3 = (LogRecord)ois2.readObject(); // Format the new deserialized LogRecord using the SimpleFormatter, and // check that the string representation obtained matches the string // representation of the original LogRecord String str3 = formatter.format(record3); if (!str.equals(str3)) throw new RuntimeException("Unexpected values in deserialized object:" + "\n\tExpected: " + str + "\n\tRetrieved: "+str); //System.out.println(base64); //System.out.println(); // Generates the Java Pseudo code that can be cut & pasted into // this test (see Jdk8SerializedLog and Jdk9SerializedLog below) final StringBuilder sb = new StringBuilder(); sb.append(" /**").append('\n'); sb.append(" * Base64 encoded string for LogRecord object.").append('\n'); sb.append(" * Java version: ").append(System.getProperty("java.version")).append('\n'); sb.append(" **/").append('\n'); sb.append(" final String base64 = ").append("\n "); final int last = base64.length() - 1; for (int i=0; i<base64.length();i++) { if (i%64 == 0) sb.append("\""); sb.append(base64.charAt(i)); if (i%64 == 63 || i == last) { sb.append("\""); if (i == last) sb.append(";\n"); else sb.append("\n + "); } } sb.append('\n'); sb.append(" /**").append('\n'); sb.append(" * SimpleFormatter output for LogRecord object.").append('\n'); sb.append(" * Java version: ").append(System.getProperty("java.version")).append('\n'); sb.append(" **/").append('\n'); sb.append(" final String str = ").append("\n "); sb.append("\"").append(str.replace("\n", "\\n")).append("\";\n"); return sb.toString(); } /** * An abstract class to test that a log record previously serialized on a * different java version can be deserialized in the current java version. * (see Jdk8SerializedLog and Jdk9SerializedLog below) */ public abstract static class SerializedLog { public abstract String getBase64(); public abstract String getString(); /** * Deserializes the Base64 encoded string returned by {@link * #getBase64()}, format the obtained LogRecord using a * SimpleFormatter, and checks that the string representation obtained * matches the original string representation returned by {@link * #getString()}. */ protected void dotest() { try { final String base64 = getBase64(); final ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64)); final ObjectInputStream ois = new ObjectInputStream(bais); final LogRecord record = (LogRecord)ois.readObject(); final SimpleFormatter formatter = new SimpleFormatter(); String expected = getString(); String str2 = formatter.format(record); check(expected, str2); System.out.println(str2); System.out.println("PASSED: "+this.getClass().getName()+"\n"); } catch (IOException | ClassNotFoundException x) { throw new RuntimeException(x); } } /** * Check that the actual String representation obtained matches the * expected String representation. * @param expected Expected String representation, as returned by * {@link #getString()}. * @param actual Actual String representation obtained by formatting * the LogRecord obtained by the deserialization of the * bytes encoded in {@link #getBase64()}. */ protected void check(String expected, String actual) { if (!expected.equals(actual)) { throw new RuntimeException(this.getClass().getName() + " - Unexpected values in deserialized object:" + "\n\tExpected: " + expected + "\n\tRetrieved: "+ actual); } } } public static class Jdk8SerializedLog extends SerializedLog { // Generated by generate() on JDK 8. // -------------------------------- // BEGIN /** * Base64 encoded string for LogRecord object. * Java version: 1.8.0_11 **/ final String base64 = "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMACkoA" + "Bm1pbGxpc0oADnNlcXVlbmNlTnVtYmVySQAIdGhyZWFkSURMAAVsZXZlbHQAGUxq" + "YXZhL3V0aWwvbG9nZ2luZy9MZXZlbDtMAApsb2dnZXJOYW1ldAASTGphdmEvbGFu" + "Zy9TdHJpbmc7TAAHbWVzc2FnZXEAfgACTAAScmVzb3VyY2VCdW5kbGVOYW1lcQB+" + "AAJMAA9zb3VyY2VDbGFzc05hbWVxAH4AAkwAEHNvdXJjZU1ldGhvZE5hbWVxAH4A" + "AkwABnRocm93bnQAFUxqYXZhL2xhbmcvVGhyb3dhYmxlO3hwAAABSjUCgo0AAAAA" + "AAAAAAAAAAFzcgAXamF2YS51dGlsLmxvZ2dpbmcuTGV2ZWyOiHETUXM2kgIAA0kA" + "BXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3VyY2VCdW5kbGVOYW1lcQB+AAJ4cAAA" + "AyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2luZy5yZXNvdXJjZXMubG9nZ2luZ3QA" + "BHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBwcHB3BgEAAAAAAXQACDEuOC4wXzEx" + "eA=="; /** * SimpleFormatter output for LogRecord object. * Java version: 1.8.0_11 **/ final String str = "Dec 10, 2014 4:22:44.621000000 PM test - INFO: Java Version: 1.8.0_11"; // ^^^ // Notice the milli second resolution above... // END // -------------------------------- @Override public String getBase64() { return base64; } @Override public String getString() { return str; } public static void test() { new Jdk8SerializedLog().dotest(); } } public static class Jdk9SerializedLog extends SerializedLog { // Generated by generate() on JDK 9. // -------------------------------- // BEGIN /** * Base64 encoded string for LogRecord object. * Java version: 1.9.0-internal **/ final String base64 = "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMAC0oA" + "Bm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVuY2VOdW1iZXJJAAh0aHJl" + "YWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5nL0xldmVsO0wACmxvZ2dl" + "ck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNzYWdlcQB+AAJMABJyZXNv" + "dXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNzTmFtZXEAfgACTAAQc291" + "cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGphdmEvbGFuZy9UaHJvd2Fi" + "bGU7eHAAAAFLl3u6OAAOU/gAAAAAAAAAAAAAAAFzcgAXamF2YS51dGlsLmxvZ2dp" + "bmcuTGV2ZWyOiHETUXM2kgIAA0kABXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3Vy" + "Y2VCdW5kbGVOYW1lcQB+AAJ4cAAAAyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2lu" + "Zy5yZXNvdXJjZXMubG9nZ2luZ3QABHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBw" + "cHB3BgEAAAAAAXQADjEuOS4wLWludGVybmFseA=="; /** * SimpleFormatter output for LogRecord object. * Java version: 1.9.0-internal **/ final String str = "Feb 17, 2015 12:20:43.192939000 PM test - INFO: Java Version: 1.9.0-internal"; // ^^^ // Notice the micro second resolution above... // END // -------------------------------- @Override public String getBase64() { return base64; } @Override public String getString() { return str; } @Override protected void check(String expected, String actual) { if (System.getProperty("java.version").startsWith("1.8")) { // If we are in JDK 8 and print a log record serialized in JDK 9, // then we won't be able to print anything below the millisecond // precision, since that hasn't been implemented in JDK 8. // Therefore - we need to replace anything below millseconds by // zeroes in the expected string (which was generated on JDK 9). Pattern pattern = Pattern.compile("^" + "(.*\\.[0-9][0-9][0-9])" // group1: everything up to milliseconds + "([0-9][0-9][0-9][0-9][0-9][0-9])" // group 2: micros and nanos + "(.* - .*)$"); // group three: all the rest... Matcher matcher = pattern.matcher(expected); if (matcher.matches()) { expected = matcher.group(1) + "000000" + matcher.group(3); } } super.check(expected, actual); } public static void test() { new Jdk9SerializedLog().dotest(); } } public static void generate() { try { LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}"); record.setLoggerName("test"); record.setParameters(new Object[] {System.getProperty("java.version")}); System.out.println(generate(record)); } catch (IOException | ClassNotFoundException x) { throw new RuntimeException(x); } } static enum TestCase { GENERATE, TESTJDK8, TESTJDK9 }; public static void main(String[] args) { // Set the locale and time zone to make sure we won't depend on the // test env - in particular we don't want to depend on the // time zone in which the test machine might be located. // So we're gong to use Locale English and Time Zone UTC for this test. // (Maybe that should be Locale.ROOT?) Locale.setDefault(Locale.ENGLISH); TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC"))); // Set the format property to make sure we always have the nanos, and // to make sure it's the same format than what we used when // computing the formatted string for Jdk8SerializedLog and // Jdk9SerializedLog above. // // If you change the formatting, then you will need to regenerate // the data for Jdk8SerializedLog and Jdk9SerializedLog. // // To do that - just run this test on JDK 8, and cut & paste the // pseudo code printed by generate() into Jdk8SerializedLog. // Then run this test again on JDK 9, and cut & paste the // pseudo code printed by generate() into Jdk9SerializedLog. // [Note: you can pass GENERATE as single arg to main() to avoid // running the actual test] // Finally run the test again to check that it still passes after // your modifications. // System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s - %4$s: %5$s%6$s"); // If no args, then run everything.... if (args == null || args.length == 0) { args = new String[] { "GENERATE", "TESTJDK8", "TESTJDK9" }; } // Run the specified test case(s) Stream.of(args).map(x -> TestCase.valueOf(x)).forEach((x) -> { switch(x) { case GENERATE: generate(); break; case TESTJDK8: Jdk8SerializedLog.test(); break; case TESTJDK9: Jdk9SerializedLog.test(); break; } }); } }