1 /*
2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.StringWriter;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 
36 import javax.lang.model.element.Modifier;
37 import javax.tools.JavaCompiler;
38 import javax.tools.JavaFileObject;
39 import javax.tools.StandardJavaFileManager;
40 import javax.tools.ToolProvider;
41 
42 import com.sun.source.tree.BlockTree;
43 import com.sun.source.tree.BreakTree;
44 import com.sun.source.tree.CaseTree;
45 import com.sun.source.tree.ClassTree;
46 import com.sun.source.tree.CompilationUnitTree;
47 import com.sun.source.tree.ContinueTree;
48 import com.sun.source.tree.DoWhileLoopTree;
49 import com.sun.source.tree.ExpressionStatementTree;
50 import com.sun.source.tree.ForLoopTree;
51 import com.sun.source.tree.IfTree;
52 import com.sun.source.tree.ImportTree;
53 import com.sun.source.tree.LabeledStatementTree;
54 import com.sun.source.tree.LineMap;
55 import com.sun.source.tree.MethodTree;
56 import com.sun.source.tree.ReturnTree;
57 import com.sun.source.tree.StatementTree;
58 import com.sun.source.tree.SwitchTree;
59 import com.sun.source.tree.Tree;
60 import com.sun.source.tree.WhileLoopTree;
61 import com.sun.source.util.SourcePositions;
62 import com.sun.source.util.Trees;
63 import com.sun.tools.javac.api.JavacTaskImpl;
64 
65 import jdk.jshell.SourceCodeAnalysis;
66 
67 import org.testng.annotations.DataProvider;
68 import org.testng.annotations.Test;
69 
70 import static java.lang.Integer.max;
71 import static java.lang.Integer.min;
72 import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
73 
74 public class CompletenessStressTest extends KullaTesting {
75     public final static String JDK_ROOT_SRC_PROP = "jdk.root.src";
76     public final static String JDK_ROOT_SRC;
77 
78     static {
79         JDK_ROOT_SRC = System.getProperty(JDK_ROOT_SRC_PROP);
80     }
81 
getSourceFile(String fileName)82     public File getSourceFile(String fileName) {
83         for (File dir : getDirectoriesToTest()) {
84             File file = new File(dir, fileName);
85             if (file.exists()) {
86                 return file;
87             }
88         }
89         throw new AssertionError("File not found: " + fileName);
90     }
91 
getDirectoriesToTest()92     public File[] getDirectoriesToTest() {
93         return new File[]{
94                 new File(JDK_ROOT_SRC, "nashorn/src"),
95                 new File(JDK_ROOT_SRC, "langtools/src"),
96                 new File(JDK_ROOT_SRC, "jaxp/src"),
97                 new File(JDK_ROOT_SRC, "jaxws/src"),
98                 new File(JDK_ROOT_SRC, "jdk/src"),
99                 new File(JDK_ROOT_SRC, "corba/src")
100         };
101     }
102 
103     @DataProvider(name = "crawler")
dataProvider()104     public Object[][] dataProvider() throws IOException {
105         File[] srcDirs = getDirectoriesToTest();
106         List<String[]> list = new ArrayList<>();
107         for (File srcDir : srcDirs) {
108             String srcDirName = srcDir.getAbsolutePath();
109             // this is just to obtain pretty test names for testng tests
110             List<String[]> a = Files.walk(Paths.get(srcDirName))
111                     .map(Path::toFile)
112                     .map(File::getAbsolutePath)
113                     .filter(n -> n.endsWith(".java"))
114                     .map(n -> n.replace(srcDirName, ""))
115                     .map(n -> new String[]{n})
116                     .collect(Collectors.toList());
117             if (a.isEmpty()) {
118                 throw new AssertionError("Java sources have not been found in directory: " + srcDirName);
119             }
120             list.addAll(a);
121         }
122         return list.toArray(new String[list.size()][]);
123     }
124 
125     @Test(dataProvider = "crawler")
testFile(String fileName)126     public void testFile(String fileName) throws IOException {
127         File file = getSourceFile(fileName);
128         final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
129         final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
130         boolean success = true;
131         StringWriter writer = new StringWriter();
132         writer.write("Testing : " + file.toString() + "\n");
133         String text = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
134         Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);
135         JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
136         Iterable<? extends CompilationUnitTree> asts = task.parse();
137         Trees trees = Trees.instance(task);
138         SourcePositions sp = trees.getSourcePositions();
139 
140         for (CompilationUnitTree cut : asts) {
141             for (ImportTree imp : cut.getImports()) {
142                 success &= testStatement(writer, sp, text, cut, imp);
143             }
144             for (Tree decl : cut.getTypeDecls()) {
145                 success &= testStatement(writer, sp, text, cut, decl);
146                 if (decl instanceof ClassTree) {
147                     ClassTree ct = (ClassTree) decl;
148                     for (Tree mem : ct.getMembers()) {
149                         if (mem instanceof MethodTree) {
150                             MethodTree mt = (MethodTree) mem;
151                             BlockTree bt = mt.getBody();
152                             // No abstract methods or constructors
153                             if (bt != null && mt.getReturnType() != null) {
154                                 // The modifiers synchronized, abstract, and default are not allowed on
155                                 // top-level declarations and are errors.
156                                 Set<Modifier> modifier = mt.getModifiers().getFlags();
157                                 if (!modifier.contains(Modifier.ABSTRACT)
158                                         && !modifier.contains(Modifier.SYNCHRONIZED)
159                                         && !modifier.contains(Modifier.DEFAULT)) {
160                                     success &= testStatement(writer, sp, text, cut, mt);
161                                 }
162                                 testBlock(writer, sp, text, cut, bt);
163                             }
164                         }
165                     }
166                 }
167             }
168         }
169         fileManager.close();
170         if (!success) {
171             throw new AssertionError(writer.toString());
172         }
173     }
174 
isLegal(StatementTree st)175     private boolean isLegal(StatementTree st) {
176         return !(st instanceof ReturnTree) &&
177                 !(st instanceof ContinueTree) && !(st instanceof BreakTree);
178     }
179 
testBranch(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, StatementTree statementTree)180     private boolean testBranch(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, StatementTree statementTree) {
181         if (statementTree instanceof BlockTree) {
182             return testBlock(writer, sp, text, cut, (BlockTree) statementTree);
183         } else if (isLegal(statementTree)) {
184             return testStatement(writer, sp, text, cut, statementTree);
185         }
186         return true;
187     }
188 
testBlock(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, BlockTree blockTree)189     private boolean testBlock(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, BlockTree blockTree) {
190         boolean success = true;
191         for (StatementTree st : blockTree.getStatements()) {
192             if (isLegal(st)) {
193                 success &= testStatement(writer, sp, text, cut, st);
194             }
195             if (st instanceof IfTree) {
196                 IfTree ifTree = (IfTree) st;
197                 success &= testBranch(writer, sp, text, cut, ifTree.getThenStatement());
198                 success &= testBranch(writer, sp, text, cut, ifTree.getElseStatement());
199             } else if (st instanceof WhileLoopTree) {
200                 WhileLoopTree whileLoopTree = (WhileLoopTree) st;
201                 success &= testBranch(writer, sp, text, cut, whileLoopTree.getStatement());
202             } else if (st instanceof DoWhileLoopTree) {
203                 DoWhileLoopTree doWhileLoopTree = (DoWhileLoopTree) st;
204                 success &= testBranch(writer, sp, text, cut, doWhileLoopTree.getStatement());
205             } else if (st instanceof ForLoopTree) {
206                 ForLoopTree forLoopTree = (ForLoopTree) st;
207                 success &= testBranch(writer, sp, text, cut, forLoopTree.getStatement());
208             } else if (st instanceof LabeledStatementTree) {
209                 LabeledStatementTree labelTree = (LabeledStatementTree) st;
210                 success &= testBranch(writer, sp, text, cut, labelTree.getStatement());
211             } else if (st instanceof SwitchTree) {
212                 SwitchTree switchTree = (SwitchTree) st;
213                 for (CaseTree caseTree : switchTree.getCases()) {
214                     for (StatementTree statementTree : caseTree.getStatements()) {
215                         success &= testBranch(writer, sp, text, cut, statementTree);
216                     }
217                 }
218             }
219         }
220         return success;
221     }
222 
testStatement(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, Tree statement)223     private boolean testStatement(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, Tree statement) {
224         if (statement == null) {
225             return true;
226         }
227         int start = (int) sp.getStartPosition(cut, statement);
228         int end = (int) sp.getEndPosition(cut, statement);
229         char ch = text.charAt(end - 1);
230         SourceCodeAnalysis.Completeness expected = COMPLETE;
231         LineMap lineMap = cut.getLineMap();
232         int row = (int) lineMap.getLineNumber(start);
233         int column = (int) lineMap.getColumnNumber(start);
234         switch (ch) {
235             case ',':
236             case ';':
237                 expected = (statement instanceof ExpressionStatementTree)
238                         ? COMPLETE
239                         : COMPLETE_WITH_SEMI;
240                 --end;
241                 break;
242             case '}':
243                 break;
244             default:
245                 writer.write(String.format("Unexpected end: row %d, column %d: '%c' -- %s\n",
246                         row, column, ch, text.substring(start, end)));
247                 return true;
248         }
249         String unit = text.substring(start, end);
250         SourceCodeAnalysis.CompletionInfo ci = getAnalysis().analyzeCompletion(unit);
251         if (ci.completeness() != expected) {
252             if (expected == COMPLETE_WITH_SEMI && (ci.completeness() == CONSIDERED_INCOMPLETE || ci.completeness() == EMPTY)) {
253                 writer.write(String.format("Empty statement: row %d, column %d: -- %s\n",
254                         start, end, unit));
255             } else {
256                 writer.write(String.format("Expected %s got %s: '%s'  row %d, column %d: -- %s\n",
257                         expected, ci.completeness(), unit, row, column, unit));
258                 return false;
259             }
260         }
261         return true;
262     }
263 }
264