TestSearchPaths.java revision 2455:01c43036a26e
1/*
2 * Copyright (c) 2014, 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/*
25 * @test
26 * @bug 7026941
27 * @summary path options ignored when reusing filemanager across tasks
28 */
29
30import java.io.File;
31import java.io.FileWriter;
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.net.URI;
35import java.nio.file.Files;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.Collections;
39import java.util.EnumSet;
40import java.util.List;
41import java.util.Objects;
42import java.util.jar.JarEntry;
43import java.util.jar.JarOutputStream;
44import java.util.regex.Matcher;
45import java.util.regex.Pattern;
46
47import javax.tools.JavaCompiler;
48import javax.tools.JavaCompiler.CompilationTask;
49import javax.tools.JavaFileObject;
50import javax.tools.SimpleJavaFileObject;
51import javax.tools.StandardJavaFileManager;
52import javax.tools.StandardLocation;
53import javax.tools.ToolProvider;
54
55import static javax.tools.StandardLocation.*;
56
57/**
58 * Test for combinations of using javac command-line options and fileManager setLocation
59 * calls to affect the locations available in the fileManager.
60 *
61 * Using a single Java compiler and file manager, for each of the standard locations,
62 * a series of operations is performed, using either compiler options or setLocation
63 * calls. Each operation includes a compilation, and then a check for the value of
64 * the standard location available in the file manager.
65 *
66 * The operations generate and use unique files to minimize the possibility of false
67 * positive results.
68 */
69public class TestSearchPaths {
70
71    public static void main(String... args) throws Exception {
72        TestSearchPaths t = new TestSearchPaths();
73        t.run();
74    }
75
76    void run() throws Exception {
77        compiler = ToolProvider.getSystemJavaCompiler();
78        fileManager = compiler.getStandardFileManager(null, null, null);
79
80        // basic output path
81        testClassOutput();
82
83        // basic search paths
84        testClassPath();
85        testSourcePath();
86        testPlatformClassPath();
87
88        // annotation processing
89        testAnnotationProcessorPath();
90        testSourceOutput();
91
92        // javah equivalent
93        testNativeHeaderOutput();
94
95        // future-proof: guard against new StandardLocations being added
96        if (!tested.equals(EnumSet.allOf(StandardLocation.class))) {
97            error("not all standard locations have been tested");
98            out.println("not yet tested: " + EnumSet.complementOf(tested));
99        }
100
101        if (errors > 0) {
102            throw new Exception(errors + " errors occurred");
103        }
104    }
105
106    void testClassOutput() throws IOException {
107        String test = "testClassOutput";
108
109        for (int i = 1; i <= 5; i++) {
110            File classes = createDir(test + "/" + i + "/classes");
111            List<String> options;
112            switch (i) {
113                default:
114                    options = getOptions("-d", classes.getPath());
115                    break;
116
117                case 3:
118                    setLocation(CLASS_OUTPUT, classes);
119                    options = null;
120                    break;
121            }
122            List<JavaFileObject> sources = getSources("class C" + i + " { }");
123            callTask(options, sources);
124            checkPath(CLASS_OUTPUT, Mode.EQUALS, classes);
125            checkFile(CLASS_OUTPUT, "C" + i + ".class");
126        }
127
128        tested.add(CLASS_OUTPUT);
129    }
130
131    void testClassPath() throws IOException {
132        String test = "testClassPath";
133
134        for (int i = 1; i <= 5; i++) {
135            File classes = createDir(test + "/" + i + "/classes");
136            File classpath = new File("testClassOutput/" + i + "/classes");
137            List<String> options;
138            switch (i) {
139                default:
140                    options = getOptions("-d", classes.getPath(), "-classpath", classpath.getPath());
141                    break;
142
143                case 3:
144                    setLocation(CLASS_PATH, classpath);
145                    options = getOptions("-d", classes.getPath());
146                    break;
147
148                case 4:
149                    options = getOptions("-d", classes.getPath(), "-cp", classpath.getPath());
150                    break;
151            }
152            List<JavaFileObject> sources = getSources("class D" + i + " { C" + i + " c; }");
153            callTask(options, sources);
154            checkPath(CLASS_PATH, Mode.EQUALS, classpath);
155            checkFile(CLASS_OUTPUT, "D" + i + ".class");
156        }
157
158        tested.add(CLASS_PATH);
159    }
160
161    void testSourcePath() throws IOException {
162        String test = "testSourcePath";
163        setLocation(CLASS_PATH); // empty
164
165        for (int i = 1; i <= 5; i++) {
166            File src = createDir(test + "/" + i + "/src");
167            writeFile(src, "C" + i + ".java", "class C" + i + "{ }");
168            File classes = createDir(test + "/" + i + "/classes");
169            File srcpath = src;
170            List<String> options;
171            switch (i) {
172                default:
173                    options = getOptions("-d", classes.getPath(), "-sourcepath", srcpath.getPath());
174                    break;
175
176                case 3:
177                    setLocation(SOURCE_PATH, srcpath);
178                    options = getOptions("-d", classes.getPath());
179                    break;
180            }
181            List<JavaFileObject> sources = getSources("class D" + i + " { C" + i + " c; }");
182            callTask(options, sources);
183            checkPath(SOURCE_PATH, Mode.EQUALS, srcpath);
184            checkFile(CLASS_OUTPUT, "D" + i + ".class");
185        }
186
187        tested.add(SOURCE_PATH);
188    }
189
190    void testPlatformClassPath() throws IOException {
191        String test = "testPlatformClassPath";
192
193        List<File> defaultPath = getLocation(PLATFORM_CLASS_PATH);
194        StringBuilder sb = new StringBuilder();
195        for (File f: defaultPath) {
196            if (sb.length() > 0)
197                sb.append(File.pathSeparator);
198            sb.append(f);
199        }
200        String defaultPathString = sb.toString();
201
202        setLocation(CLASS_PATH); // empty
203        setLocation(SOURCE_PATH); // empty
204
205        for (int i = 1; i <= 10; i++) {
206            File classes = createDir(test + "/" + i + "/classes");
207            File testJars = createDir(test + "/" + i + "/testJars");
208            File testClasses = createDir(test + "/" + i + "/testClasses");
209            callTask(getOptions("-d", testClasses.getPath()), getSources("class C" + i + " { }"));
210
211            List<String> options;
212            Mode mode;
213            List<File> match;
214            String reference = "C" + i + " c;";
215
216            File jar;
217
218            switch (i) {
219                case 1:
220                    options = getOptions("-d", classes.getPath(), "-Xbootclasspath/p:" + testClasses);
221                    mode = Mode.STARTS_WITH;
222                    match = Arrays.asList(testClasses);
223                    break;
224
225                case 2:
226                    // the default values for -extdirs and -endorseddirs come after the bootclasspath;
227                    // so to check -Xbootclasspath/a: we specify empty values for those options.
228                    options = getOptions("-d", classes.getPath(),
229                            "-Xbootclasspath/a:" + testClasses,
230                            "-extdirs", "",
231                            "-endorseddirs", "");
232                    mode = Mode.ENDS_WITH;
233                    match = Arrays.asList(testClasses);
234                    break;
235
236                case 3:
237                    options = getOptions("-d", classes.getPath(), "-Xbootclasspath:" + defaultPathString);
238                    mode = Mode.EQUALS;
239                    match = defaultPath;
240                    reference = "";
241                    break;
242
243                case 4:
244                    fileManager.setLocation(PLATFORM_CLASS_PATH, null);
245                    jar = new File(testJars, "j" + i + ".jar");
246                    writeJar(jar, testClasses, "C" + i + ".class");
247                    options = getOptions("-d", classes.getPath(), "-endorseddirs", testJars.getPath());
248                    mode = Mode.CONTAINS;
249                    match = Arrays.asList(jar);
250                    break;
251
252                case 5:
253                    fileManager.setLocation(PLATFORM_CLASS_PATH, null);
254                    jar = new File(testJars, "j" + i + ".jar");
255                    writeJar(jar, testClasses, "C" + i + ".class");
256                    options = getOptions("-d", classes.getPath(), "-Djava.endorsed.dirs=" + testJars.getPath());
257                    mode = Mode.CONTAINS;
258                    match = Arrays.asList(jar);
259                    break;
260
261                case 6:
262                    fileManager.setLocation(PLATFORM_CLASS_PATH, null);
263                    jar = new File(testJars, "j" + i + ".jar");
264                    writeJar(jar, testClasses, "C" + i + ".class");
265                    options = getOptions("-d", classes.getPath(), "-extdirs", testJars.getPath());
266                    mode = Mode.CONTAINS;
267                    match = Arrays.asList(jar);
268                    break;
269
270                case 7:
271                    fileManager.setLocation(PLATFORM_CLASS_PATH, null);
272                    jar = new File(testJars, "j" + i + ".jar");
273                    writeJar(jar, testClasses, "C" + i + ".class");
274                    options = getOptions("-d", classes.getPath(), "-Djava.ext.dirs=" + testJars.getPath());
275                    mode = Mode.CONTAINS;
276                    match = Arrays.asList(jar);
277                    break;
278
279                case 8:
280                    setLocation(PLATFORM_CLASS_PATH, defaultPath);
281                    options = getOptions("-d", classes.getPath());
282                    mode = Mode.EQUALS;
283                    match = defaultPath;
284                    reference = "";
285                    break;
286
287                default:
288                    options = getOptions("-d", classes.getPath(), "-bootclasspath", defaultPathString);
289                    mode = Mode.EQUALS;
290                    match = defaultPath;
291                    reference = "";
292                    break;
293            }
294            List<JavaFileObject> sources = getSources("class D" + i + " { " + reference + " }");
295
296            callTask(options, sources);
297            checkPath(PLATFORM_CLASS_PATH, mode, match);
298            checkFile(CLASS_OUTPUT, "D" + i + ".class");
299        }
300
301        tested.add(PLATFORM_CLASS_PATH);
302    }
303
304    void testAnnotationProcessorPath() throws IOException {
305        String test = "testAnnotationProcessorPath";
306
307        String template =
308                "import java.util.*;\n"
309                + "import javax.annotation.processing.*;\n"
310                + "import javax.lang.model.*;\n"
311                + "import javax.lang.model.element.*;\n"
312                + "@SupportedAnnotationTypes(\"*\")\n"
313                + "public class A%d extends AbstractProcessor {\n"
314                + "    public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n"
315                + "        return true;\n"
316                + "    }\n"
317                + "    public SourceVersion getSupportedSourceVersion() {\n"
318                + "        return SourceVersion.latest();\n"
319                + "    }\n"
320                + "}";
321
322        for (int i = 1; i <= 5; i++) {
323            File classes = createDir(test + "/" + i + "/classes");
324            File annodir = createDir(test + "/" + i + "/processors");
325            callTask(getOptions("-d", annodir.getPath()), getSources(String.format(template, i)));
326            File annopath = annodir;
327            List<String> options;
328            switch (i) {
329                default:
330                    options = getOptions("-d", classes.getPath(),
331                            "-processorpath", annopath.getPath(),
332                            "-processor", "A" + i);
333                    break;
334
335                case 3:
336                    setLocation(ANNOTATION_PROCESSOR_PATH, annopath);
337                    options = getOptions("-d", classes.getPath(),
338                            "-processor", "A" + i);
339                    break;
340            }
341            List<JavaFileObject> sources = getSources("class D" + i + " { }");
342            callTask(options, sources);
343            checkPath(ANNOTATION_PROCESSOR_PATH, Mode.EQUALS, annopath);
344            checkFile(CLASS_OUTPUT, "D" + i + ".class");
345        }
346
347        tested.add(ANNOTATION_PROCESSOR_PATH);
348    }
349
350    void testSourceOutput() throws IOException {
351        String test = "testAnnotationProcessorPath";
352
353        String source =
354                "import java.io.*;\n"
355                + "import java.util.*;\n"
356                + "import javax.annotation.processing.*;\n"
357                + "import javax.lang.model.*;\n"
358                + "import javax.lang.model.element.*;\n"
359                + "import javax.tools.*;\n"
360                + "@SupportedOptions(\"name\")\n"
361                + "@SupportedAnnotationTypes(\"*\")\n"
362                + "public class A extends AbstractProcessor {\n"
363                + "    int round = 0;\n"
364                + "    public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n"
365                + "        if (round++ == 0) try {\n"
366                + "            String name = processingEnv.getOptions().get(\"name\");\n"
367                + "            JavaFileObject fo = processingEnv.getFiler().createSourceFile(name);\n"
368                + "            try (Writer out = fo.openWriter()) {\n"
369                + "                out.write(\"class \" + name + \" { }\");\n"
370                + "            }\n"
371                + "        } catch (IOException e) { throw new Error(e); }\n"
372                + "        return true;\n"
373                + "    }\n"
374                + "    public SourceVersion getSupportedSourceVersion() {\n"
375                + "        return SourceVersion.latest();\n"
376                + "    }\n"
377                + "}";
378
379        File annodir = createDir(test + "/processors");
380        callTask(getOptions("-d", annodir.getPath()), getSources(source));
381        setLocation(ANNOTATION_PROCESSOR_PATH, annodir);
382
383        for (int i = 1; i <= 5; i++) {
384            File classes = createDir(test + "/" + i + "/classes");
385            File genSrc = createDir(test + "/" + "/genSrc");
386            List<String> options;
387            switch (i) {
388                default:
389                    options = getOptions("-d", classes.getPath(),
390                            "-processor", "A", "-Aname=G" + i,
391                            "-s", genSrc.getPath());
392                    break;
393
394                case 3:
395                    setLocation(SOURCE_OUTPUT, genSrc);
396                    options = getOptions("-d", classes.getPath(),
397                            "-processor", "A", "-Aname=G" + i);
398                    break;
399            }
400            List<JavaFileObject> sources = getSources("class D" + i + " { }");
401            callTask(options, sources);
402            checkPath(SOURCE_OUTPUT, Mode.EQUALS, genSrc);
403            checkFile(CLASS_OUTPUT, "D" + i + ".class");
404            checkFile(CLASS_OUTPUT, "G" + i + ".class");
405        }
406        tested.add(SOURCE_OUTPUT);
407    }
408
409    void testNativeHeaderOutput() throws IOException {
410        String test = "testNativeHeaderOutput";
411
412        for (int i = 1; i <= 5; i++) {
413            File classes = createDir(test + "/" + i + "/classes");
414            File headers = createDir(test + "/" + i + "/hdrs");
415            List<String> options;
416            switch (i) {
417                default:
418                    options = getOptions("-d", classes.getPath(), "-h", headers.getPath());
419                    break;
420
421                case 3:
422                    setLocation(NATIVE_HEADER_OUTPUT, headers);
423                    options = getOptions("-d", classes.getPath());
424                    break;
425            }
426            List<JavaFileObject> sources = getSources("class C" + i + " { native void m(); }");
427            callTask(options, sources);
428            checkPath(NATIVE_HEADER_OUTPUT, Mode.EQUALS, headers);
429            checkFile(NATIVE_HEADER_OUTPUT, "C" + i + ".h");
430        }
431
432        tested.add(StandardLocation.NATIVE_HEADER_OUTPUT);
433    }
434
435    List<String> getOptions(String... args) {
436        return Arrays.asList(args);
437    }
438
439    List<JavaFileObject> getSources(String... sources) {
440        List<JavaFileObject> list = new ArrayList<>();
441        for (String s: sources)
442            list.add(getSource(s));
443        return list;
444    }
445
446    JavaFileObject getSource(final String source) {
447        return new SimpleJavaFileObject(getURIFromSource(source), JavaFileObject.Kind.SOURCE) {
448            @Override
449            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
450                return source;
451            }
452        };
453    }
454
455    void callTask(List<String> options, List<JavaFileObject> files) {
456        out.print("compile: ");
457        if (options != null) {
458            for (String o: options) {
459                if (o.length() > 64) {
460                    o = o.substring(0, 32) + "..." + o.substring(o.length() - 32);
461                }
462                out.print(" " + o);
463            }
464        }
465        for (JavaFileObject f: files)
466            out.print(" " + f.getName());
467        out.println();
468        CompilationTask t = compiler.getTask(out, fileManager, null, options, null, files);
469        boolean ok = t.call();
470        if (!ok)
471            error("compilation failed");
472    }
473
474    enum Mode { EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH };
475
476    void checkFile(StandardLocation l, String path) {
477        if (!l.isOutputLocation()) {
478            error("Not an output location: " + l);
479            return;
480        }
481
482        List<File> files = getLocation(l);
483        if (files == null) {
484            error("location is unset: " + l);
485            return;
486        }
487
488        if (files.size() != 1)
489            error("unexpected number of entries on " + l + ": " + files.size());
490
491        File f = new File(files.get(0), path);
492        if (!f.exists())
493            error("file not found: " + f);
494    }
495
496    void checkPath(StandardLocation l, Mode m, File expect) {
497        checkPath(l, m, Arrays.asList(expect));
498    }
499
500    void checkPath(StandardLocation l, Mode m, List<File> expect) {
501        List<File> files = getLocation(l);
502        if (files == null) {
503            error("location is unset: " + l);
504            return;
505        }
506
507        switch (m) {
508            case EQUALS:
509                if (!Objects.equals(files, expect)) {
510                    error("location does not match the expected files: " + l);
511                    out.println("found:  " + files);
512                    out.println("expect: " + expect);
513                }
514                break;
515
516            case CONTAINS:
517                int containsIndex = Collections.indexOfSubList(files, expect);
518                if (containsIndex == -1) {
519                    error("location does not contain the expected files: " + l);
520                    out.println("found:  " + files);
521                    out.println("expect: " + expect);
522                }
523            break;
524
525            case STARTS_WITH:
526                int startsIndex = Collections.indexOfSubList(files, expect);
527                if (startsIndex != 0) {
528                    error("location does not start with the expected files: " + l);
529                    out.println("found:  " + files);
530                    out.println("expect: " + expect);
531                }
532            break;
533
534            case ENDS_WITH:
535                int endsIndex = Collections.lastIndexOfSubList(files, expect);
536                if (endsIndex != files.size() - expect.size()) {
537                    error("location does not end with the expected files: " + l);
538                    out.println("found:  " + files);
539                    out.println("expect: " + expect);
540                }
541            break;
542
543        }
544    }
545
546    List<File> getLocation(StandardLocation l) {
547        Iterable<? extends File> iter = fileManager.getLocation(l);
548        if (iter == null)
549            return null;
550        List<File> files = new ArrayList<>();
551        for (File f: iter)
552            files.add(f);
553        return files;
554    }
555
556    void setLocation(StandardLocation l, File... files) throws IOException {
557        fileManager.setLocation(l, Arrays.asList(files));
558    }
559
560    void setLocation(StandardLocation l, List<File> files) throws IOException {
561        fileManager.setLocation(l, files);
562    }
563
564    void writeFile(File dir, String path, String body) throws IOException {
565        try (FileWriter w = new FileWriter(new File(dir, path))) {
566            w.write(body);
567        }
568    }
569
570    void writeJar(File jar, File dir, String... entries) throws IOException {
571        try (JarOutputStream j = new JarOutputStream(Files.newOutputStream(jar.toPath()))) {
572            for (String entry: entries) {
573                j.putNextEntry(new JarEntry(entry));
574                j.write(Files.readAllBytes(dir.toPath().resolve(entry)));
575            }
576        }
577    }
578
579    private static final Pattern packagePattern
580            = Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))");
581    private static final Pattern classPattern
582            = Pattern.compile("(?:public\\s+)?(?:class|enum|interface)\\s+(\\w+)");
583
584
585    private static URI getURIFromSource(String source) {
586        String packageName = null;
587
588        Matcher matcher = packagePattern.matcher(source);
589        if (matcher.find()) {
590            packageName = matcher.group(1).replace(".", "/");
591        }
592
593        matcher = classPattern.matcher(source);
594        if (matcher.find()) {
595            String className = matcher.group(1);
596            String path = ((packageName == null) ? "" : packageName + "/") + className + ".java";
597            return URI.create("myfo:///" + path);
598        } else {
599            throw new Error("Could not extract the java class "
600                    + "name from the provided source");
601        }
602    }
603
604    File createDir(String path) {
605        File dir = new File(path);
606        dir.mkdirs();
607        return dir;
608    }
609
610    JavaCompiler compiler;
611    StandardJavaFileManager fileManager;
612
613    /**
614     * Map for recording which standard locations have been tested.
615     */
616    EnumSet<StandardLocation> tested = EnumSet.noneOf(StandardLocation.class);
617
618    /**
619     * Logging stream. Used directly with test and for getTask calls.
620     */
621    final PrintWriter out = new PrintWriter(System.err, true);
622
623    /**
624     * Count of errors so far.
625     */
626    int errors;
627
628    void error(String message) {
629        errors++;
630        out.println("Error: " + message);
631    }
632}
633