darwin.js 21.4 KB
Newer Older
Takter.10's avatar
Takter.10 committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
'use strict'

const config = require('./config.json')
const exec = require('mz/child_process').exec
const fs = require('fs-extra')
const mac = require('../mac')
const packager = require('..')
const path = require('path')
const plist = require('plist')
const test = require('ava')
const util = require('./_util')

const darwinOpts = {
  name: 'darwinTest',
  dir: util.fixtureSubdir('basic'),
  electronVersion: config.version,
  arch: 'x64',
  platform: 'darwin'
}

const el0374Opts = Object.assign({}, darwinOpts, {
  name: 'el0374Test',
  dir: util.fixtureSubdir('el-0374'),
  electronVersion: '0.37.4'
})

function testWrapper (testName, extraOpts, testFunction/*, ...extraArgs */) {
  const extraArgs = Array.prototype.slice.call(arguments, 3)

  util.packagerTest(testName, (t, baseOpts) => {
    const opts = Object.assign({}, baseOpts, extraOpts)

    return testFunction.apply(null, [t, opts].concat(extraArgs))
  })
}

function darwinTest (testName, testFunction/*, ...extraArgs */) {
  const extraArgs = Array.prototype.slice.call(arguments, 2)
  return testWrapper.apply(null, [testName, darwinOpts, testFunction].concat(extraArgs))
}

function electron0374Test (testName, testFunction) {
  const extraArgs = Array.prototype.slice.call(arguments, 2)
  return testWrapper.apply(null, [testName, el0374Opts, testFunction].concat(extraArgs))
}

function getHelperExecutablePath (helperName) {
  return path.join(`${helperName}.app`, 'Contents', 'MacOS', helperName)
}

function parseInfoPlist (t, opts, basePath) {
  const plistPath = path.join(basePath, `${opts.name}.app`, 'Contents', 'Info.plist')

  return fs.stat(plistPath)
    .then(stats => {
      t.true(stats.isFile(), 'The expected Info.plist file should exist')
      return fs.readFile(plistPath, 'utf8')
    }).then(file => plist.parse(file))
}

function packageAndParseInfoPlist (t, opts) {
  return packager(opts)
    .then(paths => parseInfoPlist(t, opts, paths[0]))
}

function helperAppPathsTest (t, baseOpts, extraOpts, expectedName) {
  const opts = Object.assign(baseOpts, extraOpts)
  let frameworksPath

  if (!expectedName) {
    expectedName = opts.name
  }

  return packager(opts)
    .then(paths => {
      frameworksPath = path.join(paths[0], `${expectedName}.app`, 'Contents', 'Frameworks')
      // main Helper.app is already tested in basic test suite; test its executable and the other helpers
      return fs.stat(path.join(frameworksPath, getHelperExecutablePath(`${expectedName} Helper`)))
    }).then(stats => {
      t.true(stats.isFile(), 'The Helper.app executable should reflect sanitized opts.name')
      return fs.stat(path.join(frameworksPath, `${expectedName} Helper EH.app`))
    }).then(stats => {
      t.true(stats.isDirectory(), 'The Helper EH.app should reflect sanitized opts.name')
      return fs.stat(path.join(frameworksPath, getHelperExecutablePath(`${expectedName} Helper EH`)))
    }).then(stats => {
      t.true(stats.isFile(), 'The Helper EH.app executable should reflect sanitized opts.name')
      return fs.stat(path.join(frameworksPath, `${expectedName} Helper NP.app`))
    }).then(stats => {
      t.true(stats.isDirectory(), 'The Helper NP.app should reflect sanitized opts.name')
      return fs.stat(path.join(frameworksPath, getHelperExecutablePath(`${expectedName} Helper NP`)))
    }).then(stats => t.true(stats.isFile(), 'The Helper NP.app executable should reflect sanitized opts.name'))
}

function iconTest (t, opts, icon, iconPath) {
  opts.icon = icon

  let resourcesPath

  return util.packageAndEnsureResourcesPath(t, opts)
    .then(generatedResourcesPath => {
      resourcesPath = generatedResourcesPath
      const outputPath = resourcesPath.replace(`${path.sep}${util.generateResourcesPath(opts)}`, '')
      return parseInfoPlist(t, opts, outputPath)
    }).then(obj => {
      return util.areFilesEqual(iconPath, path.join(resourcesPath, obj.CFBundleIconFile))
    }).then(equal => t.true(equal, 'installed icon file should be identical to the specified icon file'))
}

function extendInfoTest (t, baseOpts, extraPathOrParams) {
  const opts = Object.assign({}, baseOpts, {
    appBundleId: 'com.electron.extratest',
    appCategoryType: 'public.app-category.music',
    buildVersion: '3.2.1',
    extendInfo: extraPathOrParams
  })

  return packageAndParseInfoPlist(t, opts)
    .then(obj => {
      t.is(obj.TestKeyString, 'String data', 'TestKeyString should come from extendInfo')
      t.is(obj.TestKeyInt, 12345, 'TestKeyInt should come from extendInfo')
      t.is(obj.TestKeyBool, true, 'TestKeyBool should come from extendInfo')
      t.deepEqual(obj.TestKeyArray, ['public.content', 'public.data'], 'TestKeyArray should come from extendInfo')
      t.deepEqual(obj.TestKeyDict, { Number: 98765, CFBundleVersion: '0.0.0' }, 'TestKeyDict should come from extendInfo')
      t.is(obj.CFBundleVersion, opts.buildVersion, 'CFBundleVersion should reflect buildVersion argument')
      t.is(obj.CFBundleIdentifier, 'com.electron.extratest', 'CFBundleIdentifier should reflect appBundleId argument')
      t.is(obj.LSApplicationCategoryType, 'public.app-category.music', 'LSApplicationCategoryType should reflect appCategoryType argument')
      return t.is(obj.CFBundlePackageType, 'APPL', 'CFBundlePackageType should be Electron default')
    })
}

function binaryNameTest (t, baseOpts, extraOpts, expectedExecutableName, expectedAppName) {
  const opts = Object.assign({}, baseOpts, extraOpts)
  const appName = expectedAppName || expectedExecutableName || opts.name
  const executableName = expectedExecutableName || opts.name

  let binaryPath

  return packager(opts)
    .then(paths => {
      binaryPath = path.join(paths[0], `${appName}.app`, 'Contents', 'MacOS')
      return fs.stat(path.join(binaryPath, executableName))
    }).then(stats => t.true(stats.isFile(), 'The binary should reflect a sanitized opts.name'))
}

function appVersionTest (t, opts, appVersion, buildVersion) {
  opts.appVersion = appVersion
  opts.buildVersion = buildVersion || appVersion

  return packageAndParseInfoPlist(t, opts)
    .then(obj => {
      t.is(obj.CFBundleVersion, '' + opts.buildVersion, 'CFBundleVersion should reflect buildVersion')
      t.is(obj.CFBundleShortVersionString, '' + opts.appVersion, 'CFBundleShortVersionString should reflect appVersion')
      t.is(typeof obj.CFBundleVersion, 'string', 'CFBundleVersion should be a string')
      return t.is(typeof obj.CFBundleShortVersionString, 'string', 'CFBundleShortVersionString should be a string')
    })
}

function appBundleTest (t, opts, appBundleId) {
  if (appBundleId) {
    opts.appBundleId = appBundleId
  }

  const defaultBundleName = `com.electron.${opts.name.toLowerCase()}`
  const appBundleIdentifier = mac.filterCFBundleIdentifier(opts.appBundleId || defaultBundleName)

  return packageAndParseInfoPlist(t, opts)
    .then(obj => {
      t.is(obj.CFBundleDisplayName, opts.name, 'CFBundleDisplayName should reflect opts.name')
      t.is(obj.CFBundleName, opts.name, 'CFBundleName should reflect opts.name')
      t.is(obj.CFBundleIdentifier, appBundleIdentifier, 'CFBundleName should reflect opts.appBundleId or fallback to default')
      t.is(typeof obj.CFBundleDisplayName, 'string', 'CFBundleDisplayName should be a string')
      t.is(typeof obj.CFBundleName, 'string', 'CFBundleName should be a string')
      t.is(typeof obj.CFBundleIdentifier, 'string', 'CFBundleIdentifier should be a string')
      return t.is(/^[a-zA-Z0-9-.]*$/.test(obj.CFBundleIdentifier), true, 'CFBundleIdentifier should allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)')
    })
}

function appHelpersBundleTest (t, opts, helperBundleId, appBundleId) {
  let tempPath, plistPath

  if (helperBundleId) {
    opts.helperBundleId = helperBundleId
  }
  if (appBundleId) {
    opts.appBundleId = appBundleId
  }
  const defaultBundleName = `com.electron.${opts.name.toLowerCase()}`
  const appBundleIdentifier = mac.filterCFBundleIdentifier(opts.appBundleId || defaultBundleName)
  const helperBundleIdentifier = mac.filterCFBundleIdentifier(opts.helperBundleId || appBundleIdentifier + '.helper')

  return packager(opts)
    .then(paths => {
      tempPath = paths[0]
      plistPath = path.join(tempPath, opts.name + '.app', 'Contents', 'Frameworks', opts.name + ' Helper.app', 'Contents', 'Info.plist')
      return fs.stat(plistPath)
    }).then(stats => {
      t.true(stats.isFile(), 'The expected Info.plist file should exist in helper app')
      return fs.readFile(plistPath, 'utf8')
    }).then(file => {
      const obj = plist.parse(file)
      t.is(obj.CFBundleName, opts.name, 'CFBundleName should reflect opts.name in helper app')
      t.is(obj.CFBundleIdentifier, helperBundleIdentifier, 'CFBundleIdentifier should reflect opts.helperBundleId, opts.appBundleId or fallback to default in helper app')
      t.is(typeof obj.CFBundleName, 'string', 'CFBundleName should be a string in helper app')
      t.is(typeof obj.CFBundleIdentifier, 'string', 'CFBundleIdentifier should be a string in helper app')
      t.is(/^[a-zA-Z0-9-.]*$/.test(obj.CFBundleIdentifier), true, 'CFBundleIdentifier should allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)')
      // check helper EH
      plistPath = path.join(tempPath, opts.name + '.app', 'Contents', 'Frameworks', opts.name + ' Helper EH.app', 'Contents', 'Info.plist')
      return fs.stat(plistPath)
    }).then(stats => {
      t.true(stats.isFile(), 'The expected Info.plist file should exist in helper EH app')
      return fs.readFile(plistPath, 'utf8')
    }).then(file => {
      const obj = plist.parse(file)
      t.is(obj.CFBundleName, opts.name + ' Helper EH', 'CFBundleName should reflect opts.name in helper EH app')
      t.is(obj.CFBundleDisplayName, opts.name + ' Helper EH', 'CFBundleDisplayName should reflect opts.name in helper EH app')
      t.is(obj.CFBundleExecutable, opts.name + ' Helper EH', 'CFBundleExecutable should reflect opts.name in helper EH app')
      t.is(obj.CFBundleIdentifier, helperBundleIdentifier + '.EH', 'CFBundleName should reflect opts.helperBundleId, opts.appBundleId or fallback to default in helper EH app')
      t.is(typeof obj.CFBundleName, 'string', 'CFBundleName should be a string in helper EH app')
      t.is(typeof obj.CFBundleDisplayName, 'string', 'CFBundleDisplayName should be a string in helper EH app')
      t.is(typeof obj.CFBundleExecutable, 'string', 'CFBundleExecutable should be a string in helper EH app')
      t.is(typeof obj.CFBundleIdentifier, 'string', 'CFBundleIdentifier should be a string in helper EH app')
      t.is(/^[a-zA-Z0-9-.]*$/.test(obj.CFBundleIdentifier), true, 'CFBundleIdentifier should allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)')
      // check helper NP
      plistPath = path.join(tempPath, opts.name + '.app', 'Contents', 'Frameworks', opts.name + ' Helper NP.app', 'Contents', 'Info.plist')
      return fs.stat(plistPath)
    }).then(stats => {
      t.true(stats.isFile(), 'The expected Info.plist file should exist in helper NP app')
      return fs.readFile(plistPath, 'utf8')
    }).then(file => {
      const obj = plist.parse(file)
      t.is(obj.CFBundleName, opts.name + ' Helper NP', 'CFBundleName should reflect opts.name in helper NP app')
      t.is(obj.CFBundleDisplayName, opts.name + ' Helper NP', 'CFBundleDisplayName should reflect opts.name in helper NP app')
      t.is(obj.CFBundleExecutable, opts.name + ' Helper NP', 'CFBundleExecutable should reflect opts.name in helper NP app')
      t.is(obj.CFBundleIdentifier, helperBundleIdentifier + '.NP', 'CFBundleName should reflect opts.helperBundleId, opts.appBundleId or fallback to default in helper NP app')
      t.is(typeof obj.CFBundleName, 'string', 'CFBundleName should be a string in helper NP app')
      t.is(typeof obj.CFBundleDisplayName, 'string', 'CFBundleDisplayName should be a string in helper NP app')
      t.is(typeof obj.CFBundleExecutable, 'string', 'CFBundleExecutable should be a string in helper NP app')
      t.is(typeof obj.CFBundleIdentifier, 'string', 'CFBundleIdentifier should be a string in helper NP app')
      return t.is(/^[a-zA-Z0-9-.]*$/.test(obj.CFBundleIdentifier), true, 'CFBundleIdentifier should allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)')
    })
}

if (!(process.env.CI && process.platform === 'win32')) {
  darwinTest('helper app paths test', helperAppPathsTest)
  darwinTest('helper app paths test with app name needing sanitization', helperAppPathsTest, {name: '@username/package-name'}, '@username-package-name')

  const iconBase = path.join(__dirname, 'fixtures', 'monochrome')
  const icnsPath = `${iconBase}.icns`

  darwinTest('icon test: .icns specified', iconTest, icnsPath, icnsPath)
  // This test exists because the .icns file basename changed as of 0.37.4
  electron0374Test('icon test: Electron 0.37.4, .icns specified', iconTest, icnsPath, icnsPath)
  darwinTest('icon test: .ico specified (should replace with .icns)', iconTest, `${iconBase}.ico`, icnsPath)
  darwinTest('icon test: basename only (should add .icns)', iconTest, iconBase, icnsPath)

  const extraInfoPath = path.join(__dirname, 'fixtures', 'extrainfo.plist')
  const extraInfoParams = plist.parse(fs.readFileSync(extraInfoPath).toString())

  darwinTest('extendInfo by filename test', extendInfoTest, extraInfoPath)
  darwinTest('extendInfo by params test', extendInfoTest, extraInfoParams)

  darwinTest('protocol/protocol-name argument test', (t, opts) => {
    opts.protocols = [
      {
        name: 'Foo',
        schemes: ['foo']
      },
      {
        name: 'Bar',
        schemes: ['bar', 'baz']
      }
    ]

    return packageAndParseInfoPlist(t, opts)
      .then(obj =>
        t.deepEqual(obj.CFBundleURLTypes, [{
          CFBundleURLName: 'Foo',
          CFBundleURLSchemes: ['foo']
        }, {
          CFBundleURLName: 'Bar',
          CFBundleURLSchemes: ['bar', 'baz']
        }], 'CFBundleURLTypes did not contain specified protocol schemes and names')
      )
  })

  test('osxSign argument test: default args', t => {
    const args = true
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {identity: null, app: 'out', platform: 'darwin', version: 'version'})
  })

  test('osxSign argument test: identity=true sets autodiscovery mode', t => {
    const args = {identity: true}
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {identity: null, app: 'out', platform: 'darwin', version: 'version'})
  })

  test('osxSign argument test: entitlements passed to electron-osx-sign', t => {
    const args = {entitlements: 'path-to-entitlements'}
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {app: 'out', platform: 'darwin', version: 'version', entitlements: args.entitlements})
  })

  test('osxSign argument test: app not overwritten', t => {
    const args = {app: 'some-other-path'}
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {app: 'out', platform: 'darwin', version: 'version'})
  })

  test('osxSign argument test: platform not overwritten', t => {
    const args = {platform: 'mas'}
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {app: 'out', platform: 'darwin', version: 'version'})
  })

  test('osxSign argument test: binaries not set', t => {
    const args = {binaries: ['binary1', 'binary2']}
    const signOpts = mac.createSignOpts(args, 'darwin', 'out', 'version')
    t.deepEqual(signOpts, {app: 'out', platform: 'darwin', version: 'version'})
  })

  darwinTest('codesign test', (t, opts) => {
    opts.osxSign = {identity: 'Developer CodeCert'}

    let appPath

    return packager(opts)
      .then(paths => {
        appPath = path.join(paths[0], opts.name + '.app')
        return fs.stat(appPath)
      }).then(stats => {
        t.true(stats.isDirectory(), 'The expected .app directory should exist')
        return exec(`codesign -v ${appPath}`)
      }).then(
        (stdout, stderr) => t.pass('codesign should verify successfully'),
        err => {
          const notFound = err && err.code === 127

          if (notFound) {
            console.log('codesign not installed; skipped')
          } else {
            throw err
          }
        }
      )
  })

  darwinTest('binary naming test', binaryNameTest)
  darwinTest('sanitized binary naming test', binaryNameTest, {name: '@username/package-name'}, '@username-package-name')
  darwinTest('executableName test', binaryNameTest, {executableName: 'app-name', name: 'MyAppName'}, 'app-name', 'MyAppName')

  darwinTest('CFBundleName is the sanitized app name and CFBundleDisplayName is the non-sanitized app name', (t, opts) => {
    const appBundleIdentifier = 'com.electron.username-package-name'
    const expectedSanitizedName = '@username-package-name'

    let plistPath

    opts.name = '@username/package-name'

    return packager(opts)
      .then(paths => {
        plistPath = path.join(paths[0], `${expectedSanitizedName}.app`, 'Contents', 'Info.plist')
        return fs.stat(plistPath)
      }).then(stats => {
        t.true(stats.isFile(), 'The expected Info.plist file should exist')
        return fs.readFile(plistPath, 'utf8')
      }).then(file => {
        const obj = plist.parse(file)
        t.is(typeof obj.CFBundleDisplayName, 'string', 'CFBundleDisplayName should be a string')
        t.is(obj.CFBundleDisplayName, opts.name, 'CFBundleDisplayName should reflect opts.name')
        t.is(typeof obj.CFBundleName, 'string', 'CFBundleName should be a string')
        t.is(obj.CFBundleName, expectedSanitizedName, 'CFBundleName should reflect a sanitized opts.name')
        t.is(typeof obj.CFBundleIdentifier, 'string', 'CFBundleIdentifier should be a string')
        t.is(/^[a-zA-Z0-9-.]*$/.test(obj.CFBundleIdentifier), true, 'CFBundleIdentifier should allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)')
        return t.is(obj.CFBundleIdentifier, appBundleIdentifier, 'CFBundleIdentifier should reflect the sanitized opts.name')
      })
  })

  darwinTest('app and build version test', appVersionTest, '1.1.0', '1.1.0.1234')
  darwinTest('app version test', appVersionTest, '1.1.0')
  darwinTest('app and build version integer test', appVersionTest, 12, 1234)
  darwinTest('infer app version from package.json test', (t, opts) =>
    packageAndParseInfoPlist(t, opts)
      .then(obj => {
        t.is(obj.CFBundleVersion, '4.99.101', 'CFBundleVersion should reflect package.json version')
        return t.is(obj.CFBundleShortVersionString, '4.99.101', 'CFBundleShortVersionString should reflect package.json version')
      })
  )

  darwinTest('app categoryType test', (t, opts) => {
    const appCategoryType = 'public.app-category.developer-tools'
    opts.appCategoryType = appCategoryType

    return packageAndParseInfoPlist(t, opts)
      .then(obj => t.is(obj.LSApplicationCategoryType, appCategoryType, 'LSApplicationCategoryType should reflect opts.appCategoryType'))
  })

  darwinTest('app bundle test', appBundleTest, 'com.electron.basetest')
  darwinTest('app bundle (w/ special characters) test', appBundleTest, 'com.electron."bãśè tëßt!@#$%^&*()?\'')
  darwinTest('app bundle app-bundle-id fallback test', appBundleTest)

  darwinTest('app bundle framework symlink test', (t, opts) => {
    let frameworkPath

    return packager(opts)
      .then(paths => {
        frameworkPath = path.join(paths[0], `${opts.name}.app`, 'Contents', 'Frameworks', 'Electron Framework.framework')
        return fs.stat(frameworkPath)
      }).then(stats => {
        t.true(stats.isDirectory(), 'Expected Electron Framework.framework to be a directory')
        return fs.lstat(path.join(frameworkPath, 'Electron Framework'))
      }).then(stats => {
        t.true(stats.isSymbolicLink(), 'Expected Electron Framework.framework/Electron Framework to be a symlink')
        return fs.lstat(path.join(frameworkPath, 'Versions', 'Current'))
      }).then(stats => t.true(stats.isSymbolicLink(), 'Expected Electron Framework.framework/Versions/Current to be a symlink'))
  })

  darwinTest('app helpers bundle test', appHelpersBundleTest, 'com.electron.basetest.helper')
  darwinTest('app helpers bundle (w/ special characters) test', appHelpersBundleTest, 'com.electron."bãśè tëßt!@#$%^&*()?\'.hęłpėr')
  darwinTest('app helpers bundle helper-bundle-id fallback to app-bundle-id test', appHelpersBundleTest, null, 'com.electron.basetest')
  darwinTest('app helpers bundle helper-bundle-id fallback to app-bundle-id (w/ special characters) test', appHelpersBundleTest, null, 'com.electron."bãśè tëßt!!@#$%^&*()?\'')
  darwinTest('app helpers bundle helper-bundle-id & app-bundle-id fallback test', appHelpersBundleTest)

  darwinTest('appCopyright/NSHumanReadableCopyright test', (t, baseOpts) => {
    const copyright = 'Copyright © 2003–2015 Organization. All rights reserved.'
    const opts = Object.assign({}, baseOpts, {appCopyright: copyright})

    return packageAndParseInfoPlist(t, opts)
      .then(info => t.is(info.NSHumanReadableCopyright, copyright, 'NSHumanReadableCopyright should reflect opts.appCopyright'))
  })

  darwinTest('app named Electron packaged successfully', (t, baseOpts) => {
    const opts = Object.assign({}, baseOpts, {name: 'Electron'})
    let appPath

    return packager(opts)
      .then(paths => {
        appPath = path.join(paths[0], 'Electron.app')
        return fs.stat(appPath)
      }).then(stats => {
        t.true(stats.isDirectory(), 'The Electron.app folder exists')
        return fs.stat(path.join(appPath, 'Contents', 'MacOS', 'Electron'))
      }).then(stats => t.true(stats.isFile(), 'The Electron.app/Contents/MacOS/Electron binary exists'))
  })
}