8040131: Implement classfile test for LineNumberTable attribute.
authorjjg
Thu, 16 Oct 2014 14:31:37 -0700
changeset 27126 0fa6f84c1195
parent 27125 6c718c5086b3
child 27128 d1480cb49283
8040131: Implement classfile test for LineNumberTable attribute. Reviewed-by: jjg, shurailine
langtools/test/tools/javac/classfiles/attributes/LineNumberTable/Container.java
langtools/test/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTest.java
langtools/test/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java
langtools/test/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/LineNumberTable/Container.java	Thu Oct 16 14:31:37 2014 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014, 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.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Source code template container. Contains methods for inserting one template inside another one and for generating
+ * final sources replacing placeholder by language construction.
+ */
+public class Container {
+
+    private static final String TEMPLATE_LEVEL = "#LEVEL";
+    private static final String SUB_TEMPLATE = "#SUB_TEMPLATE";
+
+    private String template;
+    private int level;
+
+    Container(String template) {
+        this.template = template;
+    }
+
+    public String getTemplate() {
+        return template;
+    }
+
+    public Container insert(Container container) {
+        template = template.replace(SUB_TEMPLATE, container.getTemplate());
+        template = template.replaceAll(TEMPLATE_LEVEL, String.valueOf(level++));
+        return this;
+    }
+
+    public List<TestCase> generate(Construction... constructions) throws IOException {
+        List<TestCase> testCases = new ArrayList<>();
+        String template = getTemplate();
+
+        int lineNumberOffset = template.substring(0, template.indexOf(SUB_TEMPLATE)).split("\n").length - 1;
+        for (Construction c : constructions) {
+            String src = template.replace(SUB_TEMPLATE, c.getSource());
+            Collection<Integer> significantLines = IntStream.of(c.getExpectedLines())
+                    .mapToObj(line -> lineNumberOffset + line)
+                    .collect(toList());
+            testCases.add(new TestCase(src, significantLines, c.name()));
+        }
+        return testCases;
+    }
+
+    public interface Construction {
+        String getSource();
+
+        int[] getExpectedLines();
+
+        String name();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTest.java	Thu Oct 16 14:31:37 2014 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+/*
+ * @test
+ * @summary Tests a line number table attribute for language constructions in different containers.
+ * @bug 8040131
+ *
+ * @library /tools/lib /tools/javac/lib ../lib
+ * @build ToolBox TestBase InMemoryFileManager LineNumberTestBase Container TestCase
+ * @run main LineNumberTest
+ */
+public class LineNumberTest extends LineNumberTestBase {
+    public static void main(String[] args) throws Exception {
+        new LineNumberTest().test();
+    }
+
+    public void test() throws Exception {
+        int failed = 0;
+        for (TestData testData : TestData.values()) {
+            echo("[Executing test]: " + testData);
+            try {
+                test(testData.container);
+            } catch (Exception e) {
+                echo("[Test failed]: " + testData);
+                e.printStackTrace();
+                failed++;
+                continue;
+            }
+            echo("[Test passed]: " + testData);
+        }
+        if (failed > 0)
+            throw new RuntimeException(String.format("Failed tests %d of %d%n", failed, TestData.values().length));
+    }
+
+    enum TestData {
+        SimpleMethod(new MainContainer()),
+        LocalClassContainer(new MainContainer()
+                .insert(new LocalClassContainer())),
+        LambdaContainer(new MainContainer()
+                .insert(new LambdaContainer())),
+        LambdaInLambdaContainer(new MainContainer()
+                .insert(new LambdaContainer())
+                .insert(new LambdaContainer())),
+        LambdaInLocalClassContainerTest(new MainContainer()
+                .insert(new LocalClassContainer())
+                .insert(new LambdaContainer())),
+        LocalClassInLambdaContainer(new MainContainer()
+                .insert(new LambdaContainer())
+                .insert(new LocalClassContainer())),
+        LocalInLocalContainer(new MainContainer()
+                .insert(new LocalClassContainer())
+                .insert(new LocalClassContainer()));
+        Container container;
+
+        TestData(Container container) {
+            this.container = container;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java	Thu Oct 16 14:31:37 2014 -0700
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2014, 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 com.sun.tools.classfile.*;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.tools.JavaFileObject;
+
+import static com.sun.tools.classfile.Attribute.Code;
+import static com.sun.tools.classfile.Attribute.LineNumberTable;
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Base class for line number table attribute tests.
+ * To add new tests cases(e.g. for new language constructions) you should modify TestData in LineNumberTest.
+ * If you plan to add new tests you should extends LineNumberTestBase and invoke one of two "test(...)" methods.
+ *
+ * @see #test(Container) test methods for more info.
+ */
+public class LineNumberTestBase extends TestBase {
+    /**
+     * Generates test cases and passes to {@link #test(java.util.List)}
+     * Generation: Replaces placeholder in template by value of enum {@link Constructions}.
+     */
+    protected void test(Container container) throws Exception {
+        test(container.generate(Constructions.values()));
+    }
+
+    /**
+     * Takes list of test cases. Compiles source of test case.
+     * Checks what expected lines are covered by line number table.
+     * Does general check of line number table for consistency.
+     *
+     * @param testCases list of test cases.
+     */
+    protected void test(List<TestCase> testCases) throws Exception {
+        boolean failed = false;
+        for (TestCase testCase : testCases) {
+            try {
+                Set<Integer> coveredLines = new HashSet<>();
+                for (JavaFileObject file : compile(testCase.src).getClasses().values()) {
+                    ClassFile classFile = ClassFile.read(file.openInputStream());
+                    for (Method m : classFile.methods) {
+                        Code_attribute code_attribute = (Code_attribute) m.attributes.get(Code);
+
+                        assertEquals(
+                                countAttributes(LineNumberTable, code_attribute.attributes.attrs, classFile.constant_pool),
+                                1,
+                                "Can be more than one LNT attribute, but javac should generate only one.");
+
+                        LineNumberTable_attribute tableAttribute =
+                                (LineNumberTable_attribute) code_attribute.attributes.get(LineNumberTable);
+                        checkAttribute(testCase, tableAttribute, code_attribute.code_length);
+                        coveredLines.addAll(
+                                Stream.of(tableAttribute.line_number_table)
+                                        .map(e -> e.line_number)
+                                        .collect(toList()));
+                    }
+                }
+                assertTrue(coveredLines.containsAll(testCase.expectedLines),
+                        format("All significant lines are not covered.%n" +
+                                "Covered: %s%n" +
+                                "Expected: %s%n", coveredLines, testCase.expectedLines));
+            } catch (AssertionFailedException | CompilationException ex) {
+                System.err.printf("#       %-20s#%n", testCase.getName());
+                int l = 0;
+                for (String line : testCase.src.split("\n")) {
+                    System.err.println(++l + line);
+                }
+                System.err.println(ex);
+                failed = true;
+                continue;
+            }
+            System.err.printf("#       %-20s#%n", testCase.getName());
+            System.err.println("Passed");
+        }
+        if (failed) {
+            throw new RuntimeException("Test failed");
+        }
+    }
+
+    private int countAttributes(String name, Attribute[] attrs, ConstantPool constant_pool) throws ConstantPoolException {
+        int i = 0;
+        for (Attribute attribute : attrs) {
+            if (name.equals(attribute.getName(constant_pool))) {
+                i++;
+            }
+        }
+        return i;
+    }
+
+    private void checkAttribute(TestCase testCase, LineNumberTable_attribute tableAttribute, int code_length) {
+        assertEquals(tableAttribute.line_number_table_length, tableAttribute.line_number_table.length,
+                "Incorrect line number table length.");
+        //attribute length is offset(line_number_table_length) + element_size*element_count
+        assertEquals(tableAttribute.attribute_length, 2 + 4 * tableAttribute.line_number_table_length,
+                "Incorrect attribute length");
+        testNonEmptyLine(testCase.src.split("\n"), tableAttribute);
+        assertEquals(
+                Stream.of(tableAttribute.line_number_table)
+                        .filter(e -> e.start_pc >= code_length)
+                        .count()
+                , 0L, "StartPC is out of bounds.");
+    }
+
+    /**
+     * Expects line number table point to non empty lines.
+     * The method can't recognize commented lines as empty(insensible) in case of multiline comment.
+     */
+    private void testNonEmptyLine(String[] source, LineNumberTable_attribute attribute) {
+        for (LineNumberTable_attribute.Entry e : attribute.line_number_table) {
+            String line = source[e.line_number - 1].trim();
+            assertTrue(!("".equals(line) || line.startsWith("//") || line.startsWith("/*")),
+                    format("Expect that line #%d is not empty.%n", e.line_number));
+        }
+    }
+
+    protected static enum Constructions implements Container.Construction {
+        STORE("testField = 10;"),
+        LOAD("int p;\n" +
+                "p = testField;", 2),
+        ASSERT("assert false: \"Assert error\";"),
+        ARRAY("double arr[] = new double[10];"),
+        ARRAY2("int arr2[][] = {{1,2},{}};"),
+        LAMBDA("Runnable runnable = () -> \n" +
+                "   System.out.println();"),
+        LAMBDA_BODY("Runnable runnable = () -> {\n" +
+                "   testField++;\n" +
+                "};"),
+        METHOD_REFERENCE("Runnable run = System.out::println;\nrun.run();"),
+        INVOKE_STATIC_METHOD("System.out.println(\"\");"),
+        INVOKE_INTERFACE("Runnable runnable = new Runnable() {\n" +
+                "    @Override\n" +
+                "    public void run() {\n" +
+                "        System.out.println(\"runnable\");\n" +
+                "    }\n" +
+                "};\n" +
+                "runnable.run();", 1, 7),
+        INVOKE_VIRTUAL_METHOD("testMethod();"),
+        INVOKE_CONSTRUCTOR("new Integer(2);"),
+        INVOKE_LAMBDA(LAMBDA.getSource() + "\n" +
+                "runnable.run();"),
+        DO_WHILE("do{\n" +
+                "    testField++;\n" +
+                "}while(testField == 1);", 2, 3),
+        WHILE("while(testField == 1);"),
+        FOR("for(int i = 0; i < 3 ; i++);"),
+        FOR_ENHANCEMENT("int[] ints = {1,2,3};\n" +
+                "for(int i:  ints);"),
+        LABEL("int i=0;\n" +
+                "label:{\n" +
+                "    label2:\n" +
+                "    for(;i<5;i++){\n" +
+                "        if(i==3)\n" +
+                "            break label;\n" +
+                "        if(i==0){\n" +
+                "            continue label2;\n" +
+                "        }\n" +
+                "        return;\n" +
+                "    }\n" +
+                "    i++;\n" +
+                "}\n"
+                , 1, 4, 5, 6, 7, 8, 10, 12),
+        CONDITION("int res = \n" +
+                "testField == 2 ?\n" +
+                "10\n" +
+                ":9;", 1, 3, 4), // see issue https://bugs.openjdk.java.net/browse/JDK-8050993
+        TRY("try{\n" +
+                "    --testField;\n" +
+                "}\n" +
+                "catch(Exception e){\n" +
+                "    --testField;\n" +
+                "}\n" +
+                "catch(Error e){\n" +
+                "    System.out.print(e);\n" +
+                "    throw e;\n " +
+                "}\n" +
+                "finally{\n" +
+                "    ++testField;\n" +
+                "}", 2, 4, 5, 7, 8, 9, 12),
+        TRY_WITH_RESOURCES("try (\n" +
+                "    Writer writer = new StringWriter();\n" +
+                "    Reader reader = new StringReader(\"\")) {\n" +
+                "        writer.write(1);\n" +
+                "        reader.read();\n" +
+                "} catch (IOException e) {}\n"
+                , 2, 3, 4, 5),
+        SYNCHRONIZE("" +
+                "synchronized(this){\n" +
+                "    testField++;\n" +
+                "}"),
+        SWITCH("switch (testField){\n" +
+                "case 1:\n" +
+                "    break;\n" +
+                "case 2:\n" +
+                "    testField++;\n" +
+                "default: \n" +
+                "    testField+=2; \n" +
+                "}", 1, 3, 5, 7),
+        SWITCH_STRING(
+                "String str = String.valueOf(testField);\n" +
+                        "switch (str){\n" +
+                        "case \"1\":\n" +
+                        "    break;\n" +
+                        "case \"2\":\n" +
+                        "    testField++;\n" +
+                        "default: \n" +
+                        "    testField+=2; \n" +
+                        "}", 1, 2, 4, 6, 8);
+
+        private final String source;
+        private int[] expectedLines;
+
+        Constructions(String source) {
+            this.source = source;
+            expectedLines = IntStream.rangeClosed(1, source.split("\n").length).toArray();
+        }
+
+        Constructions(String source, int... expectedLines) {
+            this.source = source;
+            this.expectedLines = expectedLines;
+        }
+
+        @Override
+        public String getSource() {
+            return source;
+        }
+
+        @Override
+        public int[] getExpectedLines() {
+            return expectedLines;
+        }
+    }
+
+    protected static class MainContainer extends Container {
+
+        public MainContainer() {
+            super("import java.io.*;\n" +
+                    "public class Main{\n" +
+                    "    public int testField;\n" +
+                    "\n" +
+                    "    public void testMethod() {\n" +
+                    "        #SUB_TEMPLATE\n" +
+                    "    }\n" +
+                    "}");
+        }
+    }
+
+    protected static class LocalClassContainer extends Container {
+
+        public LocalClassContainer() {
+
+            super("class Local#LEVEL{\n" +
+                    "    public void m(){\n" +
+                    "        #SUB_TEMPLATE\n" +
+                    "        return;\n" +
+                    "    }" +
+                    "}");
+        }
+    }
+
+    protected static class LambdaContainer extends Container {
+
+        public LambdaContainer() {
+            super("Runnable lambda#LEVEL = () -> {\n" +
+                    "    #SUB_TEMPLATE\n" +
+                    "};\n" +
+                    "lambda#LEVEL.run();\n");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java	Thu Oct 16 14:31:37 2014 -0700
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2014, 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.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * TestCase contains source code to be compiled
+ * and expected lines to be covered by a line number table attribute.
+ */
+public class TestCase {
+    public final String src;
+    public final Set<Integer> expectedLines;
+
+
+    private final String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public TestCase(String src, Collection<Integer> expectedLines, String name) {
+        this.src = src;
+        this.expectedLines = new HashSet<>(expectedLines);
+        this.name = name;
+    }
+}