1 /*
2  * Copyright (c) 2014, 2016, 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 com.sun.tools.javac.api.JavacTool;
25 import com.sun.tools.javac.file.JavacFileManager;
26 import com.sun.tools.javac.util.Context;
27 import java.io.ByteArrayOutputStream;
28 import java.io.FileWriter;
29 import java.io.IOException;
30 import java.io.PrintStream;
31 import java.io.PrintWriter;
32 import java.io.StringWriter;
33 import java.io.UncheckedIOException;
34 import java.lang.annotation.Annotation;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.lang.reflect.InvocationTargetException;
38 import java.lang.reflect.Method;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.EnumMap;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.stream.Collectors;
49 import javax.tools.JavaFileManager;
50 import javax.tools.JavaFileObject;
51 import javax.tools.StandardJavaFileManager;
52 import javax.tools.ToolProvider;
53 
54 public class OptionModesTester {
55 
56     /** Marker annotation for test methods to be invoked by runTests. */
57     @Retention(RetentionPolicy.RUNTIME)
58     @interface Test { }
59 
60     /**
61      * Run all methods annotated with @Test, and throw an exception if any
62      * errors are reported..
63      * Typically called on a tester object in main()
64      * @throws Exception if any errors occurred
65      */
runTests()66     void runTests() throws Exception {
67         for (Method m: getClass().getDeclaredMethods()) {
68             Annotation a = m.getAnnotation(Test.class);
69             if (a != null) {
70                 try {
71                     out.println("Running test " + m.getName());
72                     m.invoke(this);
73                 } catch (InvocationTargetException e) {
74                     Throwable cause = e.getCause();
75                     throw (cause instanceof Exception) ? ((Exception) cause) : e;
76                 }
77                 out.println();
78             }
79         }
80         if (errors > 0)
81             throw new Exception(errors + " errors occurred");
82     }
83 
runMain(String[] opts, String[] files)84     TestResult runMain(String[] opts, String[] files) {
85         out.println("Main " + Arrays.toString(opts) + " " + Arrays.toString(files));
86         return run(new TestResult(opts), (tr, c, pw) -> {
87             com.sun.tools.javac.main.Main compiler =
88                 new com.sun.tools.javac.main.Main("javac", pw);
89             int rc = compiler.compile(join(opts, files), c).exitCode;
90             tr.setResult(rc);
91         });
92     }
93 
runCall(String[] opts, String[] files)94     TestResult runCall(String[] opts, String[] files) {
95         out.println("Call " + Arrays.toString(opts) + " " + Arrays.toString(files));
96         return run(new TestResult(opts), (tr, c, pw) -> {
97             boolean ok = JavacTool.create()
98                     .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c)
99                     .call();
100             tr.setResult(ok);
101         });
102     }
103 
104     TestResult runParse(String[] opts, String[] files) {
105         out.println("Parse " + Arrays.toString(opts) + " " + Arrays.toString(files));
106         return run(new TestResult(opts), (tr, c, pw) -> {
107             JavacTool.create()
108                     .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c)
109                     .parse();
110             tr.setResult(true);
111         });
112     }
113 
114     TestResult runAnalyze(String[] opts, String[] files) {
115         out.println("Analyze " + Arrays.toString(opts) + " " + Arrays.toString(files));
116         return run(new TestResult(opts), (tr, c, pw) -> {
117             JavacTool.create()
118                     .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c)
119                     .analyze();
120             tr.setResult(true);
121         });
122     }
123 
124     interface Runnable {
125         void run(TestResult tr, Context c, PrintWriter pw) throws IOException;
126     }
127 
128     TestResult run(TestResult tr, Runnable r) {
129         StringWriter sw = new StringWriter();
130         PrintWriter pw = new PrintWriter(sw);
131         StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
132         StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
133         Context context = new Context();
134         JavacFileManager.preRegister(context);
135         try {
136             r.run(tr, context, pw);
137         } catch (IllegalArgumentException | IllegalStateException | IOException e) {
138             tr.setThrown(e);
139         } finally {
140             try {
141                 ((JavacFileManager) context.get(JavaFileManager.class)).close();
142             } catch (IOException e) {
143                 throw new UncheckedIOException(e);
144             }
145             tr.setLogs(sw.toString(), sysOut.close(), sysErr.close());
146         }
147         tr.setContext(context);
148         tr.show();
149         return tr;
150     }
151 
152     enum Log { DIRECT, STDOUT, STDERR };
153 
154     class TestResult {
155         final List<String> args;
156         Throwable thrown;
157         List<Throwable> suppressed = new ArrayList<>();
158         Object rc; // Number or Boolean
159         Map<Log, String> logs;
160         Context context;
161 
162         TestResult(String... args) {
163             this.args = Arrays.asList(args);
164         }
165 
166         TestResult(List<String> args, Iterable<? extends JavaFileObject> files) {
167             this.args = new ArrayList<>();
168             this.args.addAll(args);
169             for (JavaFileObject f: files)
170                 this.args.add(f.getName());
171         }
172 
173         void setResult(int rc) {
174             this.rc = rc;
175         }
176 
177         void setResult(boolean ok) {
178             this.rc = ok ? 0 : 1;
179         }
180 
181         void setSuppressed(Throwable thrown) {
182             this.suppressed.add(thrown);
183         }
184 
185         void setThrown(Throwable thrown) {
186             this.thrown = thrown;
187         }
188 
189         void setLogs(String direct, String stdOut, String stdErr) {
190             logs = new EnumMap<>(Log.class);
191             logs.put(Log.DIRECT, direct);
192             logs.put(Log.STDOUT, stdOut);
193             logs.put(Log.STDERR, stdErr);
194         }
195 
196         void setContext(Context context) {
197             this.context = context;
198         }
199 
200         final void show() {
201             String NL = System.getProperty("line.separator");
202             boolean needSep = false;
203             if (rc != null) {
204                 out.print("rc:" + rc);
205                 needSep = true;
206             }
207             if (thrown != null) {
208                 if (needSep) out.print("; ");
209                 out.print("thrown:" + thrown);
210                 needSep = true;
211             }
212             if (!suppressed.isEmpty()) {
213                 if (needSep) out.print("; ");
214                 out.print("suppressed:" + suppressed);
215                 needSep = true;
216             }
217             if (needSep)
218                 out.println();
219             logs.forEach((k, v) -> {
220                 if (!v.isEmpty()) {
221                     out.println("javac/" + k + ":");
222                     if (v.endsWith(NL))
223                         out.print(v);
224                     else
225                         out.println(v);
226                 }
227 
228             });
229         }
230 
231         TestResult checkOK() {
232             if (thrown != null) {
233                 error("unexpected exception thrown: " + thrown);
234             } else if (rc == null) {
235                 error("no result set");
236             } else if (rc != (Integer) 0 && rc != (Boolean) true) {
237                 error("compilation failed unexpectedly; rc=" + rc);
238             }
239             return this;
240         }
241 
242         TestResult checkResult(int expect) {
243             if (thrown != null) {
244                 error("unexpected exception thrown: " + thrown);
245             } else if (rc != (Integer) expect) {
246                 error("unexpected result: " + rc +", expected:" + expect);
247             }
248             return this;
249         }
250 
251         TestResult checkResult(boolean expect) {
252             if (thrown != null) {
253                 error("unexpected exception thrown: " + thrown);
254             } else if (rc != (Integer) (expect ? 0 : 1)) {
255                 error("unexpected result: " + rc +", expected:" + expect);
256             }
257             return this;
258         }
259 
260         TestResult checkLog(String... expects) {
261             return checkLog(Log.DIRECT, expects);
262         }
263 
264         TestResult checkLog(Log l, String... expects) {
265             for (String e: expects) {
266                 if (!logs.get(l).contains(e))
267                     error("expected string not found: " + e);
268             }
269             return this;
270         }
271 
272         TestResult checkIllegalArgumentException() {
273             return checkThrown(IllegalArgumentException.class);
274         }
275 
276         TestResult checkIllegalStateException() {
277             return checkThrown(IllegalStateException.class);
278         }
279 
280         TestResult checkThrown(Class<? extends Throwable> t) {
281             if (thrown == null)
282                 error("expected exception not thrown: " + t);
283             else if (!t.isAssignableFrom(thrown.getClass()))
284                 error("unexpected exception thrown: " + thrown + ";  expected: " + t);
285             return this;
286         }
287 
288         TestResult checkClass(String name) {
289             Path p = getOutDir().resolve(name.replace(".", "/") + ".class");
290             if (!Files.exists(p))
291                 error("expected class not found: " + name + " (" + p + ")");
292             return this;
293         }
294 
295         Path getOutDir() {
296             Iterator<String> iter = args.iterator();
297             while (iter.hasNext()) {
298                 if (iter.next().equals("-d")) {
299                     return Paths.get(iter.next());
300                 }
301             }
302             return null;
303         }
304     }
305 
306     /**
307      * Utility class to simplify the handling of temporarily setting a
308      * new stream for System.out or System.err.
309      */
310     private static class StreamOutput {
311         // functional interface to set a stream.
312         private interface Initializer {
313             void set(PrintStream s);
314         }
315 
316         private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
317         private final PrintStream ps = new PrintStream(baos);
318         private final PrintStream prev;
319         private final Initializer init;
320 
321         StreamOutput(PrintStream s, Initializer init) {
322             prev = s;
323             init.set(ps);
324             this.init = init;
325         }
326 
327         String close() {
328             init.set(prev);
329             ps.close();
330             return baos.toString();
331         }
332     }
333 
334     List<JavaFileObject> getFiles(String... paths) {
335         List<JavaFileObject> files = new ArrayList<>();
336         for (JavaFileObject f : fm.getJavaFileObjects(paths))
337             files.add(f);
338         return files;
339     }
340 
341     String toString(List<JavaFileObject> files) {
342         return files.stream().map(f -> f.getName()).collect(Collectors.toList()).toString();
343     }
344 
345     void mkdirs(String path) throws IOException {
346         Files.createDirectories(Paths.get(path));
347     }
348 
349     void writeFile(String path, String body) throws IOException {
350         Path p = Paths.get(path);
351         if (p.getParent() != null)
352             Files.createDirectories(p.getParent());
353         try (FileWriter w = new FileWriter(path)) {
354             w.write(body);
355         }
356     }
357 
358     String[] join(String[] a, String[] b) {
359         String[] result = new String[a.length + b.length];
360         System.arraycopy(a, 0, result, 0, a.length);
361         System.arraycopy(b, 0, result, a.length, b.length);
362         return result;
363     }
364 
365     void error(String message) {
366         out.print(">>>>> ");
367         out.println(message);
368         errors++;
369     }
370 
371     StandardJavaFileManager fm =
372             ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
373     PrintStream out = System.err;
374     int errors;
375 
376 }
377