03e84178eb
Reviewed-by: jlahoda
398 lines
14 KiB
Java
398 lines
14 KiB
Java
/*
|
|
* Copyright (c) 2015, 2021, 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
|
|
* @bug 8149524 8131024 8165211 8080071 8130454 8167343 8129559 8114842 8182268 8223782 8235474 8246774 8276149
|
|
* @summary Test SourceCodeAnalysis
|
|
* @build KullaTesting TestingInputStream
|
|
* @run testng CompletenessTest
|
|
*/
|
|
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
import java.util.function.Consumer;
|
|
import javax.lang.model.SourceVersion;
|
|
import jdk.jshell.JShell;
|
|
|
|
import org.testng.annotations.Test;
|
|
import jdk.jshell.SourceCodeAnalysis.Completeness;
|
|
|
|
import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
|
|
import org.testng.annotations.BeforeMethod;
|
|
|
|
@Test
|
|
public class CompletenessTest extends KullaTesting {
|
|
|
|
// Add complete units that end with semicolon to complete_with_semi (without
|
|
// the semicolon). Both cases will be tested.
|
|
static final String[] complete = new String[] {
|
|
"{ x= 4; }",
|
|
"int mm(int x) {kll}",
|
|
"if (t) { ddd; }",
|
|
"for (int i = 0; i < lines.length(); ++i) { foo }",
|
|
"while (ct == null) { switch (current.kind) { case EOF: { } } }",
|
|
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched); }",
|
|
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); }",
|
|
"List<T> f() { return null; }",
|
|
"List<?> f() { return null; }",
|
|
"List<? extends Object> f() { return null; }",
|
|
"Map<? extends Object, ? super Object> f() { return null; }",
|
|
"class C { int z; }",
|
|
"synchronized (r) { f(); }",
|
|
"try { } catch (Exception ex) { }",
|
|
"try { } catch (Exception ex) { } finally { }",
|
|
"try { } finally { }",
|
|
"try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)) { }",
|
|
"foo: while (true) { printf(\"Innn\"); break foo; }",
|
|
"class Case<E1 extends Enum<E1>, E2 extends Enum<E2>, E3 extends Enum<E3>> {}",
|
|
";",
|
|
"enum Tt { FOO, BAR, BAZ,; }",
|
|
"record D(int i) {}",
|
|
"static record D(int i) {}",
|
|
};
|
|
|
|
static final String[] expression = new String[] {
|
|
"test",
|
|
"x + y",
|
|
"x + y ++",
|
|
"p = 9",
|
|
"match(BRACKETS, TokenKind.LBRACKET)",
|
|
"new C()",
|
|
"new C() { public String toString() { return \"Hi\"; } }",
|
|
"new int[]",
|
|
"new int[] {1, 2,3}",
|
|
"new Foo() {}",
|
|
"i >= 0 && Character.isWhitespace(s.charAt(i))",
|
|
"int.class",
|
|
"String.class",
|
|
"record.any",
|
|
"record()",
|
|
"record(1)",
|
|
"record.length()"
|
|
};
|
|
|
|
static final String[] complete_with_semi = new String[] {
|
|
"int mm",
|
|
"if (t) ddd",
|
|
"int p = 9",
|
|
"int p",
|
|
"Deque<Token> stack = new ArrayDeque<>()",
|
|
"final Deque<Token> stack = new ArrayDeque<>()",
|
|
"java.util.Scanner input = new java.util.Scanner(System.in)",
|
|
"java.util.Scanner input = new java.util.Scanner(System.in) { }",
|
|
"int j = -i",
|
|
"String[] a = { \"AAA\" }",
|
|
"assert true",
|
|
"int path[]",
|
|
"int path[][]",
|
|
"int path[][] = new int[22][]",
|
|
"int path[] = new int[22]",
|
|
"int path[] = new int[] {1, 2, 3}",
|
|
"int[] path",
|
|
"int path[] = new int[22]",
|
|
"int path[][] = new int[22][]",
|
|
"for (Object o : a) System.out.println(\"Yep\")",
|
|
"while (os == null) System.out.println(\"Yep\")",
|
|
"do f(); while (t)",
|
|
"if (os == null) System.out.println(\"Yep\")",
|
|
"if (t) if (!t) System.out.println(123)",
|
|
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else break",
|
|
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else continue",
|
|
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else return",
|
|
"throw ex",
|
|
"C c = new C()",
|
|
"java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)",
|
|
"BufferedReader br = new BufferedReader(new FileReader(path))",
|
|
"bar: g()",
|
|
"baz: while (true) if (t()) printf('-'); else break baz",
|
|
"java.util.function.IntFunction<int[]> ggg = int[]::new",
|
|
"List<? extends Object> l",
|
|
"int[] m = {1, 2}",
|
|
"int[] m = {1, 2}, n = null",
|
|
"int[] m = {1, 2}, n",
|
|
"int[] m = {1, 2}, n = {3, 4}",
|
|
};
|
|
|
|
static final String[] considered_incomplete = new String[] {
|
|
"if (t)",
|
|
"if (t) { } else",
|
|
"if (t) if (!t)",
|
|
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE))",
|
|
"for (int i = 0; i < 10; ++i)",
|
|
"while (os == null)",
|
|
};
|
|
|
|
static final String[] definitely_incomplete = new String[] {
|
|
"int mm(",
|
|
"int mm(int x",
|
|
"int mm(int x)",
|
|
"int mm(int x) {",
|
|
"int mm(int x) {kll",
|
|
"if",
|
|
"if (",
|
|
"if (t",
|
|
"if (t) {",
|
|
"if (t) { ddd",
|
|
"if (t) { ddd;",
|
|
"if (t) if (",
|
|
"if (stack.isEmpty()) {",
|
|
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) {",
|
|
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched);",
|
|
"x +",
|
|
"x *",
|
|
"3 *",
|
|
"int",
|
|
"for (int i = 0; i < lines.length(); ++i) {",
|
|
"new",
|
|
"new C(",
|
|
"new int[",
|
|
"new int[] {1, 2,3",
|
|
"new int[] {",
|
|
"while (ct == null) {",
|
|
"while (ct == null) { switch (current.kind) {",
|
|
"while (ct == null) { switch (current.kind) { case EOF: {",
|
|
"while (ct == null) { switch (current.kind) { case EOF: { } }",
|
|
"enum TK {",
|
|
"enum TK { EOF(TokenKind.EOF, 0),",
|
|
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM)",
|
|
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); ",
|
|
"enum Tt { FOO, BAR, BAZ,;",
|
|
"class C",
|
|
"class C extends D",
|
|
"class C implements D",
|
|
"class C implements D, E",
|
|
"interface I extends D",
|
|
"interface I extends D, E",
|
|
"enum E",
|
|
"enum E implements I1",
|
|
"enum E implements I1, I2",
|
|
"@interface Anno",
|
|
"void f()",
|
|
"void f() throws E",
|
|
"@A(",
|
|
"int n = 4,",
|
|
"int n,",
|
|
"int[] m = {1, 2},",
|
|
"int[] m = {1, 2}, n = {3, 4},",
|
|
"Map<String,",
|
|
"switch (x) {",
|
|
"var v = switch (x) {",
|
|
"var v = switch (x) { case ",
|
|
"var v = switch (x) { case 0:",
|
|
"var v = switch (x) { case 0: break 12; ",
|
|
"record D",
|
|
"record D(",
|
|
"record D(String",
|
|
"record D(String i",
|
|
"record D(String i,",
|
|
"record D(String i, String",
|
|
"record D(String i, String j",
|
|
"record D(String i)",
|
|
"record D(String i, String j)",
|
|
"record D(String i) {",
|
|
"record D(String i, String j) {",
|
|
"static record D",
|
|
"static record D(",
|
|
"static record D(String",
|
|
"static record D(String i",
|
|
"static record D(String i,",
|
|
"static record D(String i, String",
|
|
"static record D(String i, String j",
|
|
"static record D(String i)",
|
|
"static record D(String i, String j)",
|
|
"static record D(String i) {",
|
|
"static record D(String i, String j) {",
|
|
//JDK-8276149:
|
|
"void t(int i) { int v = switch (i) { case 0 -> ",
|
|
"void t(int i) { int v = switch (i) { case 0 -> {",
|
|
"void t(int i) { int v = switch (i) { case 0 -> a = b;",
|
|
"void t(int i) { int v = switch (i) { case 0 -> System.err.println(1);",
|
|
"void t(int i) { int v = switch (i) { case 0 -> throw new IllegalStateException();",
|
|
};
|
|
|
|
static final String[] unknown = new String[] {
|
|
"new ;",
|
|
"\"",
|
|
"\"\\"
|
|
};
|
|
|
|
static final Map<Completeness, String[]> statusToCases = new HashMap<>();
|
|
static {
|
|
statusToCases.put(COMPLETE, complete);
|
|
statusToCases.put(COMPLETE_WITH_SEMI, complete_with_semi);
|
|
statusToCases.put(CONSIDERED_INCOMPLETE, considered_incomplete);
|
|
statusToCases.put(DEFINITELY_INCOMPLETE, definitely_incomplete);
|
|
}
|
|
|
|
private void assertStatus(String input, Completeness status, String source) {
|
|
String augSrc;
|
|
switch (status) {
|
|
case COMPLETE_WITH_SEMI:
|
|
augSrc = source + ";";
|
|
break;
|
|
|
|
case DEFINITELY_INCOMPLETE:
|
|
augSrc = null;
|
|
break;
|
|
|
|
case CONSIDERED_INCOMPLETE:
|
|
augSrc = source + ";";
|
|
break;
|
|
|
|
case EMPTY:
|
|
case COMPLETE:
|
|
case UNKNOWN:
|
|
augSrc = source;
|
|
break;
|
|
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
assertAnalyze(input, status, augSrc);
|
|
}
|
|
|
|
private void assertStatus(String[] ins, Completeness status) {
|
|
for (String input : ins) {
|
|
assertStatus(input, status, input);
|
|
}
|
|
}
|
|
|
|
public void test_complete() {
|
|
assertStatus(complete, COMPLETE);
|
|
}
|
|
|
|
public void test_expression() {
|
|
assertStatus(expression, COMPLETE);
|
|
}
|
|
|
|
public void test_complete_with_semi() {
|
|
assertStatus(complete_with_semi, COMPLETE_WITH_SEMI);
|
|
}
|
|
|
|
public void test_considered_incomplete() {
|
|
assertStatus(considered_incomplete, CONSIDERED_INCOMPLETE);
|
|
}
|
|
|
|
public void test_definitely_incomplete() {
|
|
assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
|
|
}
|
|
|
|
public void test_unknown() {
|
|
assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
|
|
}
|
|
|
|
public void testCompleted_complete_with_semi() {
|
|
for (String in : complete_with_semi) {
|
|
String input = in + ";";
|
|
assertStatus(input, COMPLETE, input);
|
|
}
|
|
}
|
|
|
|
public void testCompleted_expression_with_semi() {
|
|
for (String in : expression) {
|
|
String input = in + ";";
|
|
assertStatus(input, COMPLETE, input);
|
|
}
|
|
}
|
|
|
|
public void testCompleted_considered_incomplete() {
|
|
for (String in : considered_incomplete) {
|
|
String input = in + ";";
|
|
assertStatus(input, COMPLETE, input);
|
|
}
|
|
}
|
|
|
|
private void assertSourceByStatus(String first) {
|
|
for (Map.Entry<Completeness, String[]> e : statusToCases.entrySet()) {
|
|
for (String in : e.getValue()) {
|
|
String input = first + in;
|
|
assertAnalyze(input, COMPLETE, first, in, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testCompleteSource_complete() {
|
|
for (String input : complete) {
|
|
assertSourceByStatus(input);
|
|
}
|
|
}
|
|
|
|
public void testCompleteSource_complete_with_semi() {
|
|
for (String in : complete_with_semi) {
|
|
String input = in + ";";
|
|
assertSourceByStatus(input);
|
|
}
|
|
}
|
|
|
|
public void testCompleteSource_expression() {
|
|
for (String in : expression) {
|
|
String input = in + ";";
|
|
assertSourceByStatus(input);
|
|
}
|
|
}
|
|
|
|
public void testCompleteSource_considered_incomplete() {
|
|
for (String in : considered_incomplete) {
|
|
String input = in + ";";
|
|
assertSourceByStatus(input);
|
|
}
|
|
}
|
|
|
|
public void testTrailingSlash() {
|
|
assertStatus("\"abc\\", UNKNOWN, "\"abc\\");
|
|
}
|
|
|
|
public void testOpenComment() {
|
|
assertStatus("int xx; /* hello", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("/** test", DEFINITELY_INCOMPLETE, null);
|
|
}
|
|
|
|
public void testTextBlocks() {
|
|
assertStatus("\"\"\"", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"broken", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"\n", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"\ntext", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"\ntext\"\"", DEFINITELY_INCOMPLETE, "\"\"\"\ntext\"\"\"");
|
|
assertStatus("\"\"\"\ntext\"\"\"", COMPLETE, "\"\"\"\ntext\"\"\"");
|
|
assertStatus("\"\"\"\ntext\\\"\"\"\"", COMPLETE, "\"\"\"\ntext\\\"\"\"\"");
|
|
assertStatus("\"\"\"\ntext\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
|
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"", COMPLETE, "\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"");
|
|
assertStatus("\"\"\"\n\\", DEFINITELY_INCOMPLETE, null);
|
|
}
|
|
|
|
public void testMiscSource() {
|
|
assertStatus("if (t) if ", DEFINITELY_INCOMPLETE, "if (t) if"); //Bug
|
|
assertStatus("int m() {} dfd", COMPLETE, "int m() {}");
|
|
assertStatus("int p = ", DEFINITELY_INCOMPLETE, "int p ="); //Bug
|
|
assertStatus("int[] m = {1, 2}, n = new int[0]; int i;", COMPLETE,
|
|
"int[] m = {1, 2}, n = new int[0];");
|
|
}
|
|
|
|
public void testInstanceOf() {
|
|
assertStatus("i instanceof Integer", COMPLETE, "i instanceof Integer");
|
|
assertStatus("i instanceof int", COMPLETE, "i instanceof int");
|
|
}
|
|
}
|