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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.tools.packager.mac;
  27 
  28 import com.oracle.tools.packager.AbstractBundler;
  29 import com.oracle.tools.packager.Bundler;
  30 import com.oracle.tools.packager.BundlerParamInfo;
  31 import com.oracle.tools.packager.ConfigException;
  32 import com.oracle.tools.packager.IOUtils;
  33 import com.oracle.tools.packager.Log;
  34 import com.oracle.tools.packager.RelativeFileSet;
  35 import com.oracle.tools.packager.UnsupportedPlatformException;
  36 import org.junit.After;
  37 import org.junit.Assume;
  38 import org.junit.Before;
  39 import org.junit.BeforeClass;
  40 import org.junit.Ignore;
  41 import org.junit.Test;
  42 
  43 import java.io.File;
  44 import java.io.IOException;
  45 import java.nio.file.Files;
  46 import java.util.ArrayList;
  47 import java.util.Arrays;
  48 import java.util.Collection;
  49 import java.util.HashMap;
  50 import java.util.HashSet;
  51 import java.util.List;
  52 import java.util.Map;
  53 import java.util.Set;
  54 import java.util.TreeMap;
  55 
  56 import static com.oracle.tools.packager.StandardBundlerParam.*;
  57 import static com.oracle.tools.packager.mac.MacAppBundler.*;
  58 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
  59 import static com.oracle.tools.packager.mac.MacPkgBundler.DEVELOPER_ID_INSTALLER_SIGNING_KEY;
  60 import static org.junit.Assert.*;
  61 
  62 public class MacAppBundlerTest {
  63 
  64     static File tmpBase;
  65     static File workDir;
  66     static File appResourcesDir;
  67     static File fakeMainJar;
  68     static File hdpiIcon;
  69     static String runtimeJdk;
  70     static String runtimeJre;
  71     static Set<File> appResources;
  72     static boolean retain = false;
  73     static boolean signingKeysPresent = false;
  74 
  75     static final File FAKE_CERT_ROOT = new File("build/tmp/tests/cert/").getAbsoluteFile();
  76 
  77     @BeforeClass
  78     public static void prepareApp() {
  79         // only run on mac
  80         Assume.assumeTrue(System.getProperty("os.name").toLowerCase().contains("os x"));
  81 
  82         runtimeJdk = System.getenv("PACKAGER_JDK_ROOT");
  83         runtimeJre = System.getenv("PACKAGER_JRE_ROOT");
  84 
  85         // and only if we have the correct JRE settings
  86         String jre = System.getProperty("java.home").toLowerCase();
  87         Assume.assumeTrue(runtimeJdk != null || jre.endsWith("/contents/home/jre") || jre.endsWith("/contents/home/jre"));
  88 
  89         Log.setLogger(new Log.Logger(true));
  90         Log.setDebug(true);
  91 
  92         retain = Boolean.parseBoolean(System.getProperty("RETAIN_PACKAGER_TESTS"));
  93 
  94         workDir = new File("build/tmp/tests", "macapp");
  95         hdpiIcon = new File("build/tmp/tests", "GenericAppHiDPI.icns");
  96         appResourcesDir = new File("build/tmp/tests", "appResources");
  97         fakeMainJar = new File(appResourcesDir, "mainApp.jar");
  98 
  99         appResources = new HashSet<>(Arrays.asList(fakeMainJar));
 100 
 101         signingKeysPresent = DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(new TreeMap<>()) != null;
 102     }
 103 
 104     @Before
 105     public void createTmpDir() throws IOException {
 106         if (retain) {
 107             tmpBase = new File("build/tmp/tests/macapp");
 108         } else {
 109             tmpBase = BUILD_ROOT.fetchFrom(new TreeMap<>());
 110         }
 111         tmpBase.mkdir();
 112     }
 113 
 114 
 115     public String createFakeCerts(Map<String, ? super Object> p) {
 116         File config = new File(FAKE_CERT_ROOT, "app-cert.cfg");
 117         config.getParentFile().mkdirs();
 118         try {
 119             // create the config file holding the key config
 120             Files.write(config.toPath(), Arrays.<String>asList("[ codesign ]",
 121                     "keyUsage=critical,digitalSignature",
 122                     "basicConstraints=critical,CA:false",
 123                     "extendedKeyUsage=critical,codeSigning"));
 124 
 125             // create the SSL keys
 126             ProcessBuilder pb = new ProcessBuilder("openssl", "req",
 127                     "-newkey", "rsa:2048",
 128                     "-nodes",
 129                     "-out", FAKE_CERT_ROOT + "/app.csr",
 130                     "-keyout", FAKE_CERT_ROOT + "/app.key",
 131                     "-subj", "/CN=Developer ID Application: Insecure Test Cert/OU=JavaFX Dev/O=Oracle/C=US");
 132             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 133 
 134             // create the cert
 135             pb = new ProcessBuilder("openssl", "x509",
 136                     "-req",
 137                     "-days", "1",
 138                     "-in", FAKE_CERT_ROOT + "/app.csr",
 139                     "-signkey", FAKE_CERT_ROOT + "/app.key",
 140                     "-out", FAKE_CERT_ROOT + "/app.crt",
 141                     "-extfile", FAKE_CERT_ROOT + "/app-cert.cfg",
 142                     "-extensions", "codesign");
 143             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 144 
 145             // create and add it to the keychain
 146             pb = new ProcessBuilder("certtool",
 147                     "i", FAKE_CERT_ROOT + "/app.crt",
 148                     "k=" + FAKE_CERT_ROOT + "/app.keychain",
 149                     "r=" + FAKE_CERT_ROOT + "/app.key",
 150                     "c",
 151                     "p=");
 152             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 153 
 154             return FAKE_CERT_ROOT + "/app.keychain";
 155         } catch (IOException e) {
 156             e.printStackTrace();
 157         }
 158 
 159         return null;
 160     }
 161 
 162     @After
 163     public void maybeCleanupTmpDir() {
 164         if (!retain) {
 165             attemptDelete(tmpBase);
 166         }
 167         attemptDelete(FAKE_CERT_ROOT);
 168     }
 169 
 170     private void attemptDelete(File tmpBase) {
 171         if (tmpBase.isDirectory()) {
 172             File[] children = tmpBase.listFiles();
 173             if (children != null) {
 174                 for (File f : children) {
 175                     attemptDelete(f);
 176                 }
 177             }
 178         }
 179         boolean success;
 180         try {
 181             success = !tmpBase.exists() || tmpBase.delete();
 182         } catch (SecurityException se) {
 183             success = false;
 184         }
 185         if (!success) {
 186             System.err.println("Could not clean up " + tmpBase.toString());
 187         }
 188     }
 189 
 190 
 191     @Test
 192     public void testValidateVersion() {
 193         MacAppBundler b = new MacAppBundler();
 194         String validVersions[] = {"1", "255", "1.0", "1.0.0", "255.255.0", "255.255.6000"};
 195         String invalidVersions[] = {null, "alpha", "1.0-alpha", "0.300", "-300", "1.-1", "1.1.-1"};
 196 
 197         for(String v: validVersions) {
 198             assertTrue("Expect to be valid ["+v+"]",
 199                     MacAppBundler.validCFBundleVersion(v));
 200             try {
 201                 Map<String, Object> params = new HashMap<>();
 202                 params.put(BUILD_ROOT.getID(), tmpBase);
 203                 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 204 
 205                 if (runtimeJdk != null) {
 206                     params.put(MAC_RUNTIME.getID(), runtimeJdk);
 207                 }
 208 
 209                 params.put(VERSION.getID(), v);
 210                 b.validate(params);
 211             } catch (ConfigException ce) {
 212                 ce.printStackTrace();
 213                 assertTrue("Expect to be valid via '" + VERSION.getID() + "' ["+v+"]",
 214                         false);
 215             } catch (UnsupportedPlatformException ignore) {
 216             }
 217             try {
 218                 Map<String, Object> params = new HashMap<>();
 219                 params.put(BUILD_ROOT.getID(), tmpBase);
 220                 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 221 
 222                 if (runtimeJdk != null) {
 223                     params.put(MAC_RUNTIME.getID(), runtimeJdk);
 224                 }
 225 
 226                 params.put(MAC_CF_BUNDLE_VERSION.getID(), v);
 227                 b.validate(params);
 228             } catch (ConfigException ce) {
 229                 assertTrue("Expect to be valid via '" + VERSION.getID() + "' ["+v+"]",
 230                         false);
 231             } catch (UnsupportedPlatformException ignore) {
 232             }
 233         }
 234 
 235         for(String v: invalidVersions) {
 236             assertFalse("Expect to be invalid ["+v+"]",
 237                     MacAppBundler.validCFBundleVersion(v));
 238             try {
 239                 Map<String, Object> params = new HashMap<>();
 240                 params.put(BUILD_ROOT.getID(), tmpBase);
 241                 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 242 
 243                 if (runtimeJdk != null) {
 244                     params.put(MAC_RUNTIME.getID(), runtimeJdk);
 245                 }
 246 
 247                 params.put(VERSION.getID(), v);
 248                 b.validate(params);
 249                 assertFalse("Invalid appVersion is not the mac.CFBundleVersion", MAC_CF_BUNDLE_VERSION.fetchFrom(params).equals(VERSION.fetchFrom(params)));
 250             } catch (ConfigException ce) {
 251                 ce.printStackTrace();
 252                 assertTrue("Expect to be ignored when invalid via '" + VERSION.getID() + "' ["+v+"]",
 253                         false);
 254             } catch (UnsupportedPlatformException ignore) {
 255             }
 256             try {
 257                 Map<String, Object> params = new HashMap<>();
 258                 params.put(BUILD_ROOT.getID(), tmpBase);
 259                 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 260 
 261                 if (runtimeJdk != null) {
 262                     params.put(MAC_RUNTIME.getID(), runtimeJdk);
 263                 }
 264 
 265                 params.put(MAC_CF_BUNDLE_VERSION.getID(), v);
 266                 b.validate(params);
 267                 assertTrue("Expect to be invalid via '" + VERSION.getID() + "' ["+v+"]",
 268                         false);
 269             } catch (ConfigException | UnsupportedPlatformException ignore) {
 270             }
 271         }
 272     }
 273 
 274 
 275     /**
 276      * See if smoke comes out
 277      */
 278     @Test
 279     public void smokeTest() throws IOException, ConfigException, UnsupportedPlatformException {
 280         AbstractBundler bundler = new MacAppBundler();
 281 
 282         assertNotNull(bundler.getName());
 283         assertNotNull(bundler.getID());
 284         assertNotNull(bundler.getDescription());
 285         //assertNotNull(bundler.getBundleParameters());
 286 
 287         Map<String, Object> bundleParams = new HashMap<>();
 288 
 289         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 290 
 291         bundleParams.put(APP_NAME.getID(), "Smoke Test App");
 292         bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Smoke");
 293         bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle");
 294         bundleParams.put(PREFERENCES_ID.getID(), "the/really/long/preferences/id");
 295         bundleParams.put(MAIN_JAR.getID(),
 296                 new RelativeFileSet(fakeMainJar.getParentFile(),
 297                         new HashSet<>(Arrays.asList(fakeMainJar)))
 298         );
 299         bundleParams.put(CLASSPATH.getID(), "mainApp.jar");
 300         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 301         bundleParams.put(VERBOSE.getID(), true);
 302         bundleParams.put(SIGN_BUNDLE.getID(), false);
 303 
 304         if (runtimeJdk != null) {
 305             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 306         }
 307 
 308         boolean valid = bundler.validate(bundleParams);
 309         assertTrue(valid);
 310 
 311         File result = bundler.execute(bundleParams, new File(workDir, "smoke"));
 312         System.err.println("Bundle at - " + result);
 313         assertNotNull(result);
 314         assertTrue(result.exists());
 315     }
 316 
 317     /**
 318      * Set File Association
 319      */
 320     @Test
 321     public void testFileAssociation()
 322         throws IOException, ConfigException, UnsupportedPlatformException
 323     {
 324         // only run the bundle with full tests
 325         Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST")));
 326 
 327         testFileAssociation("FASmoke 1", "Bogus File", "bogus", "application/x-vnd.test-bogus",
 328                             new File(appResourcesDir, "test.icns"));
 329     }
 330 
 331     @Test
 332     public void testFileAssociationWithNullExtension()
 333         throws IOException, ConfigException, UnsupportedPlatformException
 334     {
 335         // association with no extension is still valid case (see RT-38625)
 336         testFileAssociation("FASmoke null", "Bogus File", null, "application/x-vnd.test-bogus",
 337                             new File(appResourcesDir, "test.icns"));
 338     }
 339 
 340     @Test
 341     public void testFileAssociationWithMultipleExtension()
 342             throws IOException, ConfigException, UnsupportedPlatformException
 343     {
 344         // only run the bundle with full tests
 345         Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST")));
 346 
 347         testFileAssociation("FASmoke ME", "Bogus File", "bogus fake", "application/x-vnd.test-bogus",
 348                 new File(appResourcesDir, "test.icns"));
 349     }
 350 
 351     @Test
 352     public void testMultipleFileAssociation()
 353             throws IOException, ConfigException, UnsupportedPlatformException
 354     {
 355         // only run the bundle with full tests
 356         Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST")));
 357 
 358         testFileAssociationMultiples("FASmoke MA",
 359                 new String[]{"Bogus File", "Fake file"},
 360                 new String[]{"bogus", "fake"},
 361                 new String[]{"application/x-vnd.test-bogus", "application/x-vnd.test-fake"},
 362                 new File[]{new File(appResourcesDir, "test.icns"), new File(appResourcesDir, "test.icns")});
 363     }
 364 
 365     @Test
 366     public void testMultipleFileAssociationWithMultipleExtension()
 367             throws IOException, ConfigException, UnsupportedPlatformException
 368     {
 369         // association with no extension is still valid case (see RT-38625)
 370         testFileAssociationMultiples("FASmoke MAME",
 371                 new String[]{"Bogus File", "Fake file"},
 372                 new String[]{"bogus boguser", "fake faker"},
 373                 new String[]{"application/x-vnd.test-bogus", "application/x-vnd.test-fake"},
 374                 new File[]{new File(appResourcesDir, "test.icns"), new File(appResourcesDir, "test.icns")});
 375     }
 376 
 377     private void testFileAssociation(String appName, String description, String extensions,
 378                                      String contentType, File icon)
 379             throws IOException, ConfigException, UnsupportedPlatformException
 380     {
 381         testFileAssociationMultiples(appName, new String[] {description}, new String[] {extensions},
 382                 new String[] {contentType}, new File[] {icon});
 383     }
 384 
 385     private void testFileAssociationMultiples(String appName, String[] description, String[] extensions,
 386                                               String[] contentType, File[] icon)
 387             throws IOException, ConfigException, UnsupportedPlatformException
 388     {
 389         assertEquals("Sanity: description same length as extensions", description.length, extensions.length);
 390         assertEquals("Sanity: extensions same length as contentType", extensions.length, contentType.length);
 391         assertEquals("Sanity: contentType same length as icon", contentType.length, icon.length);
 392 
 393         AbstractBundler bundler = new MacAppBundler();
 394 
 395         assertNotNull(bundler.getName());
 396         assertNotNull(bundler.getID());
 397         assertNotNull(bundler.getDescription());
 398         //assertNotNull(bundler.getBundleParameters());
 399 
 400         Map<String, Object> bundleParams = new HashMap<>();
 401 
 402         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 403 
 404         if (runtimeJdk != null) {
 405             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 406         }
 407 
 408         bundleParams.put(APP_NAME.getID(), appName);
 409         bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle");
 410         bundleParams.put(MAIN_JAR.getID(),
 411                 new RelativeFileSet(fakeMainJar.getParentFile(),
 412                         new HashSet<>(Arrays.asList(fakeMainJar)))
 413         );
 414         bundleParams.put(CLASSPATH.getID(), "mainApp.jar");
 415         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 416         bundleParams.put(VERBOSE.getID(), true);
 417         bundleParams.put(SIGN_BUNDLE.getID(), false);
 418 
 419         List<Map<String, Object>> associations = new ArrayList<>();
 420 
 421         for (int i = 0; i < description.length; i++) {
 422             Map<String, Object> fileAssociation = new HashMap<>();
 423             fileAssociation.put(FA_DESCRIPTION.getID(), description[i]);
 424             fileAssociation.put(FA_EXTENSIONS.getID(), extensions[i]);
 425             fileAssociation.put(FA_CONTENT_TYPE.getID(), contentType[i]);
 426             fileAssociation.put(FA_ICON.getID(), icon[i]);
 427 
 428             associations.add(fileAssociation);
 429         }
 430 
 431         bundleParams.put(FILE_ASSOCIATIONS.getID(), associations);
 432 
 433         boolean valid = bundler.validate(bundleParams);
 434         assertTrue(valid);
 435 
 436         File result = bundler.execute(bundleParams, new File(workDir, APP_FS_NAME.fetchFrom(bundleParams)));
 437         System.err.println("Bundle at - " + result);
 438         assertNotNull(result);
 439         assertTrue(result.exists());
 440     }
 441 
 442     /**
 443      * Build signed smoke test and mark it as quarantined, skip if no keys present
 444      */
 445     @Ignore("JDK-8193232")
 446     @Test
 447     public void quarantinedAppTest() throws IOException, ConfigException, UnsupportedPlatformException {
 448         AbstractBundler bundler = new MacAppBundler();
 449 
 450         assertNotNull(bundler.getName());
 451         assertNotNull(bundler.getID());
 452         assertNotNull(bundler.getDescription());
 453         //assertNotNull(bundler.getBundleParameters());
 454 
 455         Map<String, Object> bundleParams = new HashMap<>();
 456 
 457         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 458 
 459         bundleParams.put(APP_NAME.getID(), "Quarantined Test App");
 460         bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Quarantine");
 461         bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle");
 462         bundleParams.put(PREFERENCES_ID.getID(), "the/really/long/preferences/id");
 463         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 464         bundleParams.put(VERBOSE.getID(), true);
 465 
 466         if (runtimeJdk != null) {
 467             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 468         }
 469 
 470         if (!signingKeysPresent) {
 471             String keychain = createFakeCerts(bundleParams);
 472             Assume.assumeNotNull(keychain);
 473             bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain);
 474         }
 475 
 476         boolean valid = bundler.validate(bundleParams);
 477         assertTrue(valid);
 478 
 479         File result = bundler.execute(bundleParams, new File(workDir, "quarantine"));
 480         System.err.println("Bundle at - " + result);
 481         assertNotNull(result);
 482         assertTrue(result.exists());
 483         validateSignatures(result);
 484 
 485         // mark it as though it's been downloaded
 486         ProcessBuilder pb = new ProcessBuilder(
 487                 "xattr", "-w", "com.apple.quarantine",
 488                 "0000;" + Long.toHexString(System.currentTimeMillis() / 1000L) + ";Java Unit Tests;|com.oracle.jvm.8u",
 489                 result.toString());
 490         IOUtils.exec(pb, true);
 491     }
 492 
 493     /**
 494      * The bare minimum configuration needed to make it work
 495      * <ul>
 496      *     <li>Where to build it</li>
 497      *     <li>The jar containing the application (with a main-class attribute)</li>
 498      * </ul>
 499      *
 500      * All other values will be driven off of those two values.
 501      */
 502     @Ignore("JDK-8193232")
 503     @Test
 504     public void minimumConfig() throws IOException, ConfigException, UnsupportedPlatformException {
 505         Bundler bundler = new MacAppBundler();
 506 
 507         Map<String, Object> bundleParams = new HashMap<>();
 508 
 509         // not part of the typical setup, for testing
 510         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 511 
 512         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 513 
 514         if (runtimeJdk != null) {
 515             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 516         }
 517 
 518         String keychain = null;
 519         if (!signingKeysPresent) {
 520             keychain = createFakeCerts(bundleParams);
 521             if (keychain != null) {
 522                 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain);
 523             }
 524         }
 525 
 526         File output = bundler.execute(bundleParams, new File(workDir, "BareMinimum"));
 527         System.err.println("Bundle at - " + output);
 528         assertNotNull(output);
 529         assertTrue(output.exists());
 530         if (signingKeysPresent || keychain != null) {
 531             validateSignatures(output);
 532         }
 533     }
 534 
 535     /**
 536      * Test with unicode in places we expect it to be
 537      */
 538     @Ignore("JDK-8163859")
 539     @Test
 540     public void unicodeConfig() throws IOException, ConfigException, UnsupportedPlatformException {
 541         Bundler bundler = new MacAppBundler();
 542 
 543         Map<String, Object> bundleParams = new HashMap<>();
 544 
 545         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 546 
 547         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 548 
 549         bundleParams.put(APP_NAME.getID(), "хелловорлд");
 550         bundleParams.put(TITLE.getID(), "ХеллоВорлд аппликейшн");
 551         bundleParams.put(VENDOR.getID(), "Оракл девелопмент");
 552         bundleParams.put(DESCRIPTION.getID(), "крайне большое описание со странными символами");
 553 
 554         if (runtimeJdk != null) {
 555             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 556         }
 557 
 558         String keychain = null;
 559         if (!signingKeysPresent) {
 560             keychain = createFakeCerts(bundleParams);
 561             if (keychain != null) {
 562                 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain);
 563             }
 564         }
 565 
 566         bundler.validate(bundleParams);
 567 
 568         File output = bundler.execute(bundleParams, new File(workDir, "Unicode"));
 569         System.err.println("Bundle at - " + output);
 570         assertNotNull(output);
 571         assertTrue(output.exists());
 572         if (signingKeysPresent || keychain != null) {
 573             validateSignatures(output);
 574         }
 575     }
 576 
 577     /**
 578      * Test a misconfiguration where the runtime is misconfigured.
 579      */
 580     @Test(expected = ConfigException.class)
 581     public void runtimeBad() throws IOException, ConfigException, UnsupportedPlatformException {
 582         Bundler bundler = new MacAppBundler();
 583 
 584         Map<String, Object> bundleParams = new HashMap<>();
 585 
 586         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 587         bundleParams.put(VERBOSE.getID(), true);
 588 
 589         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 590         bundleParams.put(MAC_RUNTIME.getID(), APP_RESOURCES.fetchFrom(bundleParams));
 591 
 592         bundler.validate(bundleParams);
 593     }
 594 
 595     /**
 596      * Test a misconfiguration where signature is requested but no key is kept.
 597      */
 598     @Test(expected = ConfigException.class)
 599     public void signButNoCert() throws IOException, ConfigException, UnsupportedPlatformException {
 600         Bundler bundler = new MacAppBundler();
 601 
 602         Map<String, Object> bundleParams = new HashMap<>();
 603 
 604         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 605         bundleParams.put(VERBOSE.getID(), true);
 606 
 607         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 608 
 609         bundleParams.put(SIGN_BUNDLE.getID(), true);
 610         bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null);
 611 
 612         bundler.validate(bundleParams);
 613     }
 614 
 615     @Test
 616     public void configureEverything() throws Exception {
 617         AbstractBundler bundler = new MacAppBundler();
 618         Collection<BundlerParamInfo<?>> parameters = bundler.getBundleParameters();
 619 
 620         Map<String, Object> bundleParams = new HashMap<>();
 621 
 622         bundleParams.put(APP_NAME.getID(), "Everything App Name");
 623         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 624         bundleParams.put(ARGUMENTS.getID(), Arrays.asList("He Said", "She Said"));
 625         bundleParams.put(BUNDLE_ID_SIGNING_PREFIX.getID(), "everything.signing.prefix.");
 626         bundleParams.put(CLASSPATH.getID(), "mainApp.jar");
 627         bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), "Developer ID Application");
 628         bundleParams.put(ICON_ICNS.getID(), hdpiIcon);
 629         bundleParams.put(JVM_OPTIONS.getID(), "-Xms128M");
 630         bundleParams.put(JVM_PROPERTIES.getID(), "everything.jvm.property=everything.jvm.property.value");
 631         bundleParams.put(MAC_CATEGORY.getID(), "public.app-category.developer-tools");
 632         bundleParams.put(MAC_CF_BUNDLE_IDENTIFIER.getID(), "com.example.everything.cf-bundle-identifier");
 633         bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Everything CF Bundle Name");
 634         bundleParams.put(MAC_CF_BUNDLE_VERSION.getID(), "8.2.0");
 635         bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk == null ? System.getProperty("java.home") : runtimeJdk);
 636         bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle");
 637         bundleParams.put(MAIN_JAR.getID(), "mainApp.jar");
 638         bundleParams.put(PREFERENCES_ID.getID(), "everything/preferences/id");
 639         bundleParams.put(PRELOADER_CLASS.getID(), "hello.HelloPreloader");
 640         bundleParams.put(SIGNING_KEYCHAIN.getID(), "");
 641         bundleParams.put(USER_JVM_OPTIONS.getID(), "-Xmx=256M\n");
 642         bundleParams.put(VERSION.getID(), "1.2.3.4");
 643 
 644         // assert they are set
 645         for (BundlerParamInfo bi :parameters) {
 646             assertTrue("Bundle args should contain " + bi.getID(), bundleParams.containsKey(bi.getID()));
 647         }
 648 
 649         // and only those are set
 650         bundleParamLoop:
 651         for (String s :bundleParams.keySet()) {
 652             for (BundlerParamInfo<?> bpi : parameters) {
 653                 if (s.equals(bpi.getID())) {
 654                     continue bundleParamLoop;
 655                 }
 656             }
 657             fail("Enumerated parameters does not contain " + s);
 658         }
 659 
 660         // assert they resolve
 661         for (BundlerParamInfo bi :parameters) {
 662             bi.fetchFrom(bundleParams);
 663         }
 664 
 665         // add verbose now that we are done scoping out parameters
 666         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 667         bundleParams.put(VERBOSE.getID(), true);
 668         bundleParams.put(SIGN_BUNDLE.getID(), false);
 669 
 670         // assert it validates
 671         boolean valid = bundler.validate(bundleParams);
 672         assertTrue(valid);
 673 
 674         // only run the bundle with full tests
 675         Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST")));
 676 
 677         // but first remove signing keys, test servers don't have these...
 678         bundleParams.remove(DEVELOPER_ID_APP_SIGNING_KEY.getID());
 679 
 680         File result = bundler.execute(bundleParams, new File(workDir, "everything"));
 681         System.err.println("Bundle at - " + result);
 682         assertNotNull(result);
 683         assertTrue(result.exists());
 684     }
 685 
 686     @Ignore // this test is noisy and only valid for by-hand validation
 687     @Test
 688     public void jvmUserOptionsTest() throws IOException, ConfigException, UnsupportedPlatformException {
 689 
 690         for (String name : Arrays.asList("", "example", "com.example", "com.example.helloworld", "com.example.hello.world", "com.example.hello.world.app")) {
 691 
 692             AbstractBundler bundler = new MacAppBundler();
 693 
 694             Map<String, Object> bundleParams = new HashMap<>();
 695 
 696             bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 697 
 698             bundleParams.put(APP_NAME.getID(), "User JVM Options App - " + name);
 699             bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), name + ".application");
 700             bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle");
 701             bundleParams.put(IDENTIFIER.getID(), name);
 702             bundleParams.put(PREFERENCES_ID.getID(), name.replace(".", "/"));
 703             bundleParams.put(MAIN_JAR.getID(),
 704                     new RelativeFileSet(fakeMainJar.getParentFile(),
 705                             new HashSet<>(Arrays.asList(fakeMainJar)))
 706             );
 707             bundleParams.put(CLASSPATH.getID(), "mainApp.jar");
 708             bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 709             bundleParams.put(VERBOSE.getID(), true);
 710             bundleParams.put(SIGN_BUNDLE.getID(), false); // force no signing
 711 
 712             if (runtimeJdk != null) {
 713                 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 714             }
 715 
 716             boolean valid = bundler.validate(bundleParams);
 717             assertTrue(valid);
 718 
 719             File result = bundler.execute(bundleParams, new File(workDir, "UserOpts-" + name.replace(".", "-")));
 720             System.err.println("Bundle at - " + result);
 721             assertNotNull(result);
 722             assertTrue(result.exists());
 723         }
 724     }
 725 
 726 
 727     /**
 728      * User a JRE instead of a JDK
 729      */
 730     @Ignore("JDK-8193232")
 731     @Test
 732     public void testJRE() throws IOException, ConfigException, UnsupportedPlatformException {
 733         String jre = runtimeJre == null ? "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/" : runtimeJre;
 734         Assume.assumeTrue(new File(jre).isDirectory());
 735 
 736         Bundler bundler = new MacAppBundler();
 737 
 738         Map<String, Object> bundleParams = new HashMap<>();
 739 
 740         // not part of the typical setup, for testing
 741         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 742 
 743         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 744         bundleParams.put(MAC_RUNTIME.getID(), jre);
 745 
 746         String keychain = null;
 747         if (!signingKeysPresent) {
 748             keychain = createFakeCerts(bundleParams);
 749             if (keychain != null) {
 750                 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain);
 751             }
 752         }
 753 
 754         boolean valid = bundler.validate(bundleParams);
 755         assertTrue(valid);
 756 
 757         File output = bundler.execute(bundleParams, new File(workDir, "JRETest"));
 758         System.err.println("Bundle at - " + output);
 759         assertNotNull(output);
 760         assertTrue(output.exists());
 761         if (signingKeysPresent || keychain != null) {
 762             validateSignatures(output);
 763         }
 764     }
 765 
 766     /**
 767      * Turn on AppCDS
 768      */
 769     @Ignore@Test
 770     public void testAppCDS() throws IOException, ConfigException, UnsupportedPlatformException {
 771         Bundler bundler = new MacAppBundler();
 772 
 773         Map<String, Object> bundleParams = new HashMap<>();
 774 
 775         // not part of the typical setup, for testing
 776         bundleParams.put(BUILD_ROOT.getID(), tmpBase);
 777         bundleParams.put(VERBOSE.getID(), true);
 778 
 779         bundleParams.put(APP_NAME.getID(), "AppCDSTest");
 780         bundleParams.put(IDENTIFIER.getID(), "com.example.appcds.Test");
 781         bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources));
 782         bundleParams.put(UNLOCK_COMMERCIAL_FEATURES.getID(), true);
 783         bundleParams.put(ENABLE_APP_CDS.getID(), true);
 784 
 785         if (runtimeJdk != null) {
 786             bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk);
 787         }
 788 
 789         boolean valid = bundler.validate(bundleParams);
 790         assertTrue(valid);
 791 
 792         File output = bundler.execute(bundleParams, new File(workDir, "CDSTest"));
 793         System.err.println("Bundle at - " + output);
 794         assertNotNull(output);
 795         assertTrue(output.exists());
 796     }
 797 
 798     /**
 799      * Verify a match on too many keys doesn't blow things up
 800      */
 801     @Test
 802     public void testTooManyKeyMatches() {
 803         Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Application:", null, true) != null);
 804         Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Installer:", null, true) != null);
 805         assertTrue(MacBaseInstallerBundler.findKey("Developer", null, true) == null);
 806         assertTrue(MacBaseInstallerBundler.findKey("A completely bogus key that should never realistically exist unless we are attempting to falsely break the tests", null, true) == null);
 807     }
 808 
 809     public void validateSignatures(File appLocation) throws IOException {
 810         // shallow validation
 811         ProcessBuilder pb = new ProcessBuilder(
 812                 "codesign", "--verify",
 813                 "-v", // single verbose
 814                 appLocation.getCanonicalPath());
 815         IOUtils.exec(pb, true);
 816 
 817         // deep validation
 818         pb = new ProcessBuilder(
 819                 "codesign", "--verify",
 820                 "--deep",
 821                 "-v", // single verbose
 822                 appLocation.getCanonicalPath());
 823         IOUtils.exec(pb, true);
 824 
 825         // only run spctl for pre-existing keys
 826         if (signingKeysPresent) {
 827             //spctl, this verifies gatekeeper
 828             pb = new ProcessBuilder(
 829                     "spctl", "--assess",
 830                     "-v", // single verbose
 831                     appLocation.getCanonicalPath());
 832             IOUtils.exec(pb, true);
 833         }
 834     }
 835 
 836 
 837 }