1 /*
   2  * Copyright 2007-2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @compile -XDignore.symbol.file RunMTest.java classp/MainX.java
  27  * @run main/othervm/timeout=600 RunMTest
  28  */
  29 
  30 import java.util.*;
  31 import java.io.*;
  32 import java.net.*;
  33 import java.lang.reflect.*;
  34 
  35 import javax.tools.*;
  36 
  37 import java.module.*;
  38 import java.module.annotation.MainClass;
  39 
  40 public class RunMTest {
  41 
  42     private final static char SEP = File.separatorChar;
  43 
  44     private final static String WARNING_HEADER =
  45         "// This is a machine generated file, do not edit\n" +
  46         "// Created by RunMTest v0.5\n";
  47 
  48     private final static String[] SP_HEADER = new String[] {
  49         "import java.lang.ModuleInfo.*;",
  50         "import java.module.annotation.*;",
  51         "",
  52     };
  53 
  54     private static final Map<String,String> defaultProperties;
  55 
  56     static {
  57         Properties p = System.getProperties();
  58         @SuppressWarnings("unchecked")
  59         Map<String,String> m = (Map<String,String>)(Map)p;
  60         defaultProperties = new HashMap<String,String>(m);
  61         defaultProperties.put("header", WARNING_HEADER);
  62     }
  63 
  64     protected final File file;
  65     protected final File outputDirectory;
  66     private final List<ModuleDescription> modules;
  67     private final List<TestDescription> tests;
  68 
  69     private RunMTest(File file, String baseDirectory) throws IOException {
  70         this.file = file;
  71         String mString = SEP + "mtest" + SEP;
  72         String cPath = file.getCanonicalPath();
  73         int k = cPath.lastIndexOf(mString);
  74         String subdir;
  75         if (k == -1) {
  76             subdir = file.getName();
  77         } else {
  78             subdir = cPath.substring(k + mString.length());
  79         }
  80         System.out.println(">>>Test " + subdir);
  81         outputDirectory = new File(baseDirectory, subdir);
  82         if (outputDirectory.exists()) {
  83             recursiveDelete(outputDirectory);
  84         }
  85         modules = new ArrayList<ModuleDescription>();
  86         tests = new ArrayList<TestDescription>();
  87         parse();
  88     }
  89 
  90     protected String getName() {
  91         return file.getName();
  92     }
  93 
  94     private static String template;
  95 
  96     static String getTemplate() throws IOException {
  97         if (template != null) {
  98             return template;
  99         }
 100         File f = new File(System.getProperty("test.src", "."), "cl-template.java");
 101         InputStream in = new FileInputStream(f);
 102         ByteArrayOutputStream out = new ByteArrayOutputStream();
 103         byte[] data = new byte[8192];
 104         while (true) {
 105             int n = in.read(data);
 106             if (n < 0) {
 107                 break;
 108             }
 109             out.write(data, 0, n);
 110         }
 111         in.close();
 112         byte[] b = out.toByteArray();
 113         template = new String(b, "UTF8");
 114         return template;
 115     }
 116 
 117     private void parse() throws IOException {
 118         InputStream in = new FileInputStream(file);
 119         BufferedReader reader = new BufferedReader(new InputStreamReader(in));
 120         while (true) {
 121             String line = getLine(reader);
 122             if (line == null) {
 123                 break;
 124             }
 125             if (line.trim().length() == 0) {
 126                 continue;
 127             }
 128             if (line.startsWith(">>> begin module ")) {
 129                 ModuleDescription m = parseModule(line, reader);
 130                 modules.add(m);
 131             } else if (line.startsWith(">>> begin test ")) {
 132                 TestDescription t = parseTest(line, reader);
 133                 tests.add(t);
 134             } else {
 135                 throw new IOException("Invalid declaration: " + line);
 136             }
 137         }
 138         in.close();
 139     }
 140 
 141     private String getLine(BufferedReader reader) throws IOException {
 142         while (true) {
 143             String line = reader.readLine();
 144             if (line == null) {
 145                 return null;
 146             }
 147             if (line.startsWith("#")) {
 148                 continue;
 149             }
 150             return line;
 151         }
 152     }
 153 
 154     private static void recursiveDelete(File dir) throws IOException {
 155         if (dir.isFile()) {
 156             dir.delete();
 157         } else if (dir.isDirectory()) {
 158             File[] entries = dir.listFiles();
 159             for (int i = 0; i < entries.length; i++) {
 160                 if (entries[i].isDirectory()) {
 161                     recursiveDelete(entries[i]);
 162                 }
 163                 entries[i].delete();
 164             }
 165             dir.delete();
 166         }
 167     }
 168 
 169     private static class ModuleDescription {
 170 
 171         private final String name;
 172         private final RunMTest mTest;
 173         private List<String> annotations;
 174         private List<String> imports;
 175         private List<String> exports;
 176         private List<ClassDescription> classes;
 177         private List<FileDescription> otherFiles;
 178 
 179         private List<File> sourceFiles;
 180 
 181         private File moduleDir;
 182 
 183         private ModuleDescription(RunMTest mTest, String name) {
 184             this.mTest = mTest;
 185             this.name = name;
 186             annotations = new ArrayList<String>();
 187             imports = new ArrayList<String>();
 188             exports = new ArrayList<String>();
 189             classes = new ArrayList<ClassDescription>();
 190             otherFiles = new ArrayList<FileDescription>();
 191             sourceFiles = new ArrayList<File>();
 192         }
 193 
 194         private String getMangledName() {
 195             return name.replace('.', '$');
 196         }
 197 
 198         private File getModuleDir() {
 199             if (moduleDir == null) {
 200                 String suffix = "";
 201                 for (String s : annotations) {
 202                     if (s.startsWith("@Version(\"")) {
 203                         suffix = "-" + s.substring(10, s.length() - 2);
 204                     }
 205                 }
 206                 moduleDir = new File(mTest.outputDirectory, getMangledName() + suffix);
 207             }
 208             return moduleDir;
 209         }
 210 
 211         private File write() throws IOException {
 212             File moduledir = getModuleDir();
 213             File dir = new File(moduleDir, name.replace('.', SEP));
 214             dir.mkdirs();
 215             String mangledName = getMangledName();
 216             File spfile = new File(dir, "module_info.java");
 217             PrintWriter writer = new PrintWriter(spfile);
 218             writer.println(WARNING_HEADER);
 219             writer.println("package " + mangledName + ";");
 220             writer.println();
 221             for (String s : SP_HEADER) {
 222                 writer.println(s);
 223             }
 224             for (String s : annotations) {
 225                 writer.println(s);
 226             }
 227             writer.println("class module_info {");
 228             writer.println();
 229             for (String s : exports) {
 230                 writer.println("    exports " + s.replace(".", "$") + ";");
 231             }
 232             writer.println();
 233             writer.println("}");
 234             writer.close();
 235             return spfile;
 236         }
 237 
 238     }
 239 
 240     private static String substituteProperties(String template, Map<String,String> props) {
 241         while (true) {
 242             int k = template.indexOf("${");
 243             if (k == -1) {
 244                 break;
 245             }
 246             int e = template.indexOf("}", k);
 247             String name = template.substring(k + 2, e);
 248             String prop = props.get(name);
 249             if (prop == null) {
 250                 prop = defaultProperties.get(name);
 251             }
 252             if (prop == null) {
 253                 throw new IllegalArgumentException("Property not defined: " + name);
 254             }
 255             template = template.substring(0, k) + prop + template.substring(e + 1);
 256         }
 257         return template;
 258     }
 259 
 260     private static class ClassDescription {
 261 
 262         private final String name;
 263         private final String pkg;
 264 
 265         private final Map<String,String> properties;
 266 
 267         private ClassDescription(String line) {
 268             String[] ss = line.split(" ");
 269             String s = ss[ss.length - 1];
 270             int k = s.lastIndexOf('.');
 271             pkg = s.substring(0, k);
 272             name = s.substring(k + 1);
 273             properties = new HashMap<String,String>();
 274             properties.put("name", name);
 275             properties.put("pkg", pkg);
 276             properties.put("import", "");
 277             properties.put("super", "");
 278             properties.put("info", "");
 279             properties.put("run", "");
 280             properties.put("body", "");
 281         }
 282 
 283         private void updateProperty(String propertyName, String line) {
 284             String s = properties.get(propertyName);
 285             if (s == null) {
 286                 properties.put(propertyName, line);
 287             } else {
 288                 properties.put(propertyName, s + "\n" + line);
 289             }
 290         }
 291 
 292         private File write(ModuleDescription md) throws IOException {
 293             File moduledir = md.getModuleDir();
 294             File pkgdir = new File(moduledir, pkg.replace('.', SEP));
 295             pkgdir.mkdirs();
 296             File srcfile = new File(pkgdir, name + ".java");
 297             String template = getTemplate();
 298             String srcstring = substituteProperties(template, properties);
 299             OutputStream out = new FileOutputStream(srcfile);
 300             out.write(srcstring.getBytes("UTF8"));
 301             out.close();
 302             return srcfile;
 303         }
 304 
 305     }
 306 
 307     private void runTests() throws Exception {
 308         for (TestDescription t : tests) {
 309             t.runTest(this);
 310         }
 311     }
 312 
 313 
 314     // Abstracting the creation of TestDescription instances to a factory
 315     // allows other tests to override TestDescription.runTest.
 316     //
 317     public static class TestDescriptionFactory {
 318         private static TestDescriptionFactory instance;
 319         
 320         static TestDescription create(String name) {
 321             return getInstance().doCreate(name);
 322         }
 323 
 324         private static TestDescriptionFactory getInstance() {
 325             if (instance == null) {
 326                 String factName = System.getProperty("TestDescriptionFactory.classname");
 327                 if (factName != null) {
 328                     try {
 329                         Class<?> clazz = Class.forName(factName);
 330                         instance = (TestDescriptionFactory) clazz.newInstance();
 331                     } catch (Throwable t) {
 332                         throw new RuntimeException(t);
 333                     }
 334                 } else {
 335                     instance = new TestDescriptionFactory();
 336                 }
 337             }
 338             return instance;
 339         }
 340 
 341         protected TestDescription doCreate(String name) {
 342             return new TestDescription(name);
 343         }
 344     }                
 345         
 346     public static class TestDescription {
 347 
 348         protected final String name;
 349         protected String result;
 350 
 351         TestDescription(String name) {
 352             this.name = name;
 353         }
 354 
 355         protected void runTest(RunMTest mTest) throws Exception {
 356             System.out.println("> Running test " + name + "...");
 357             Repository parent = sun.module.repository.RepositoryConfig.getSystemRepository();
 358             Repository repository = Modules.newLocalRepository(parent, mTest.getName(), mTest.outputDirectory);
 359             ModuleDefinition md = repository.find(name);
 360             try {
 361                 Module m = md.getModuleInstance();
 362                 String mainClassName = md.getAnnotation(MainClass.class).value();
 363                 ClassLoader cl = m.getClassLoader();
 364                 Class<?> clazz = cl.loadClass(mainClassName);
 365                 Method method = clazz.getMethod("main", String[].class);
 366                 method.invoke(null, (Object)new String[0]);
 367             } catch (Exception e) {
 368                 if ((e instanceof InvocationTargetException) && (e.getCause() instanceof Exception)) {
 369                     e = (Exception)e.getCause();
 370                 }
 371                 if (result.equals("return")) {
 372                     throw new Exception("test failed", e);
 373                 }
 374                 if (result.startsWith("exception ")) {
 375                     String excname = result.substring("exception ".length());
 376                     String[] fqe = e.getClass().getName().split("\\.");
 377                     if (fqe[fqe.length - 1].equals(excname)) {
 378                         System.out.println("> Test completed with expected exception: " + e);
 379                         return;
 380                     }
 381                     throw new Exception("Expected exception " + excname, e);
 382                 }
 383                 throw new Exception("Unknown test result: " + result);
 384             }
 385             if (!result.equals("return")) {
 386                 throw new Exception("Test unexpectedly returned normally");
 387             }
 388             System.out.println("> Test completed.");
 389         }
 390 
 391     }
 392 
 393     private TestDescription parseTest(String header, BufferedReader reader) throws IOException {
 394         String[] s = header.split(" ");
 395         String name = s[s.length - 1];
 396         TestDescription test = TestDescriptionFactory.create(name);
 397         while (true) {
 398             String line = getLine(reader);
 399             if (line == null) {
 400                 throw new EOFException();
 401             }
 402             if (line.equals(">>> end test")) {
 403                 break;
 404             }
 405             test.result = line;
 406         }
 407         return test;
 408     }
 409 
 410     private ClassDescription parseClass(String header, BufferedReader reader) throws IOException {
 411         ClassDescription cd = new ClassDescription(header);
 412         String prop = "run";
 413         while (true) {
 414             String line = getLine(reader);
 415             if (line == null) {
 416                 throw new EOFException();
 417             }
 418             if (line.equals(">> end class")) {
 419                 break;
 420             }
 421             if (line.startsWith("> ")) {
 422                 prop = line.substring(2).trim();
 423                 continue;
 424             }
 425             cd.updateProperty(prop, line);
 426         }
 427         return cd;
 428     }
 429 
 430     private static class FileDescription {
 431 
 432         final String name;
 433         String sourceName;
 434 
 435         private FileDescription(String line) {
 436             String[] ss = line.split(" ");
 437             name = ss[ss.length - 1];
 438         }
 439 
 440         private File write(ModuleDescription md) throws IOException {
 441             File moduledir = md.getModuleDir();
 442             File destFile = new File(moduledir, name.replace('/', SEP));
 443             destFile.getParentFile().mkdirs();
 444             File srcFile = new File(md.mTest.file.getParentFile(), sourceName.replace('/', SEP));
 445             InputStream in = new FileInputStream(srcFile);
 446             OutputStream out = new FileOutputStream(destFile);
 447             byte[] buffer = new byte[2048];
 448             while (true) {
 449                 int n = in.read(buffer);
 450                 if (n < 0) {
 451                     break;
 452                 }
 453                 out.write(buffer, 0, n);
 454             }
 455             in.close();
 456             out.close();
 457             return destFile;
 458         }
 459 
 460     }
 461 
 462     private FileDescription parseFile(String header, BufferedReader reader) throws IOException {
 463         FileDescription fd = new FileDescription(header);
 464         while (true) {
 465             String line = getLine(reader);
 466             if (line == null) {
 467                 throw new EOFException();
 468             }
 469             if (line.equals(">> end file")) {
 470                 break;
 471             }
 472             if (line.startsWith("> copy ")) {
 473                 String name = line.substring(7).trim();
 474                 fd.sourceName = name;
 475                 continue;
 476             }
 477             throw new IOException("Invalid file declaration: " + line);
 478         }
 479         return fd;
 480 
 481     }
 482 
 483     private ModuleDescription parseModule(String header, BufferedReader reader) throws IOException {
 484         String[] s = header.split(" ");
 485         String name = s[s.length - 1];
 486         ModuleDescription md = new ModuleDescription(this, name);
 487         String section = "";
 488         while (true) {
 489             String line = getLine(reader);
 490             if (line == null) {
 491                 throw new EOFException();
 492             }
 493             if (line.startsWith(">> begin class ")) {
 494                 md.classes.add(parseClass(line, reader));
 495                 continue;
 496             }
 497             if (line.startsWith(">> begin file ")) {
 498                 md.otherFiles.add(parseFile(line, reader));
 499                 continue;
 500             }
 501             if (line.startsWith("> ")) {
 502                 section = line;
 503                 continue;
 504             }
 505             if (line.equals(">>> end module")) {
 506                 break;
 507             }
 508             if (section.equals("> annotations")) {
 509                 md.annotations.add(line);
 510             } else if (section.equals("> import")) {
 511                 md.imports.add(line);
 512             } else if (section.equals("> export")) {
 513                 md.exports.add(line);
 514             } else {
 515                 throw new IOException("Invalid module declaration: " + line);
 516             }
 517         }
 518         return md;
 519     }
 520 
 521     private void createSources() throws IOException {
 522         File f;
 523         for (ModuleDescription md : modules) {
 524             md.sourceFiles.clear();
 525             f = md.write();
 526             md.sourceFiles.add(f);
 527             for (ClassDescription c : md.classes) {
 528                 f = c.write(md);
 529                 md.sourceFiles.add(f);
 530             }
 531             for (FileDescription fd : md.otherFiles) {
 532                 fd.write(md);
 533             }
 534         }
 535     }
 536 
 537     private void makeJAM() throws IOException {
 538         // since we may have multiple versions of the same module,
 539         // with types of the same name, which javac does not like, compile
 540         // each module separately
 541         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 542         StringBuilder srcpath = new StringBuilder();
 543         boolean first = true;
 544         for (ModuleDescription module : modules) {
 545             File moduleDir = module.getModuleDir();
 546             if (first) {
 547                 first = false;
 548             } else {
 549                 srcpath.append(File.pathSeparatorChar);
 550             }
 551             srcpath.append(moduleDir);
 552         }
 553         for (ModuleDescription module : modules) {
 554             StringBuilder srclist = new StringBuilder();
 555             for (File f : module.sourceFiles) {
 556                 srclist.append(f.getPath());
 557                 srclist.append(" ");
 558             }
 559             String cmd = "-source 6 -target 6 -XDignore.symbol.file -implicit:none -Xlint:all -sourcepath "
 560                 + srcpath.toString() + " " + srclist;
 561             int r = compiler.run(null, null, null, cmd.split(" "));
 562             if (r != 0) {
 563                 throw new RuntimeException("Compilation failed: " + r);
 564             }
 565 
 566             File moduleDir = module.getModuleDir();
 567             File moduleJam = new File(outputDirectory, moduleDir.getName() + ".jam");
 568 
 569             ArrayList<String> args = new ArrayList<String>();
 570             args.add("cfsS");
 571             args.add(moduleJam.getCanonicalPath());
 572             args.add(module.getMangledName());
 573             args.add(moduleDir.getCanonicalPath() + File.separator);
 574 
 575             // Presume all other entries are directories containing classes.
 576             for (File f : moduleDir.listFiles()) {
 577                 if (!f.getName().equals("MODULE-INF")) {
 578                     args.add("-C");
 579                     args.add(moduleDir.getCanonicalPath());
 580                     args.add(f.getName());
 581                 }
 582                 else {
 583                     File tf = new File(moduleDir.getCanonicalPath(), "MODULE-INF" + File.separator + "legacy-classes.list");
 584                     if (tf.exists() && !tf.isDirectory()) {
 585                         args.add("-C");
 586                         args.add(moduleDir.getCanonicalPath());
 587                         args.add("MODULE-INF" + File.separator + "legacy-classes.list");
 588                     }
 589                 }
 590             }
 591 
 592             sun.module.tools.Jam jam = new sun.module.tools.Jam(System.out, System.err, "RunMTest");
 593             jam.run(args.toArray(new String[0]));
 594         }
 595     }
 596 
 597     private static void findFiles(File directory, Collection<File> results) throws IOException {
 598         File[] files = directory.listFiles();
 599         for (File file : files) {
 600             if (file.isDirectory()) {
 601                 findFiles(file, results);
 602             }
 603             if (file.isFile() && file.getPath().endsWith(".mtest")) {
 604                 results.add(file);
 605             }
 606         }
 607     }
 608 
 609     public static void main(String[] args) throws Exception {
 610         long start = System.currentTimeMillis();
 611         if (args.length == 0) {
 612             args = new String[] { "." };
 613         }
 614         Set<File> testFiles = new TreeSet<File>();
 615         File base = new File(System.getProperty("test.src", ".") + SEP + "mtest");
 616         for (String arg : args) {
 617             File file;
 618             if (!arg.equals(".")) {
 619                 file = new File(arg);
 620                 if (file.isFile()) {
 621                     testFiles.add(file);
 622                 }
 623                 if (file.isDirectory()) {
 624                     findFiles(file, testFiles);
 625                 }
 626             }
 627             file = new File(base, arg);
 628             if (file.isFile()) {
 629                 testFiles.add(file);
 630             }
 631             if (file.isDirectory()) {
 632                 findFiles(file, testFiles);
 633             }
 634         }
 635         for (Iterator<File> t = testFiles.iterator(); t.hasNext(); ) {
 636             File f = t.next();
 637             String fname = f.getName();
 638             if (fname.startsWith("s.") || fname.startsWith("p.") || fname.startsWith(",")) {
 639                 t.remove();
 640             }
 641         }
 642         if (testFiles.isEmpty()) {
 643             throw new RuntimeException("No tests found");
 644         }
 645         String outputDir = System.getProperty("test.classes", ".") + SEP + "tmp_mtest";
 646         int modules = 0;
 647         int tests = 0;
 648         for (File file : testFiles) {
 649             RunMTest test = new RunMTest(file, outputDir);
 650             test.createSources();
 651             test.makeJAM();
 652             test.runTests();
 653             modules += test.modules.size();
 654             tests += test.tests.size();
 655         }
 656         long stop = System.currentTimeMillis();
 657         System.out.println("All tests completed ("
 658             + testFiles.size() + " mtest files, "
 659             + modules + " modules, "
 660             + tests + " tests, time "
 661             + (stop - start) + " ms)");
 662     }
 663 
 664 }