jdk/test/java/util/logging/HigherResolutionTimeStamps/SerializeLogRecord.java
author dfuchs
Wed, 25 Feb 2015 18:41:07 +0100
changeset 29117 7956b5dc0eac
child 32649 2ee9017c7597
permissions -rw-r--r--
8072645: java.util.logging should use java.time to get more precise time stamps Summary: j.u.logging uses j.t.Instant to store LogRecord time stamps. XMLFormatter format is updated to allow for a new optional <nanos> element containing a nano second adjustment. SimpleFormatter passes a ZonedDateTime object to String.format. LogRecord getMillis/setMillis are deprecated, replaced by getInstant/setInstant. Reviewed-by: scolebourne, plevart, rriggs Contributed-by: daniel.fuchs@oracle.com, peter.levart@gmail.com

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