OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface-android / test / loader.test.js

122 lines · javascript ref:
// The worklet entry that preloads OTA-staged addons and then runs the real bundle.
//
// ⛔ ORDER IS THE WHOLE POINT. Addons must be in Addon.cache BEFORE the bundle's modules evaluate —
// a `require.addon` reached first reads the cache, misses, and falls straight through to
// dlopen-by-soname against the APK. That is not recoverable afterwards, so the ordering is asserted
// here rather than assumed from the shape of the code.
import test from 'brittle'
import { loaderArgs, runLoader } from '../lib/host/loader-core.js'

const argv = (...a) => a

test('the bundle and addon paths are read by TAG, not position', (t) => {
  // ⛔ NOT POSITIONAL. bare-kit calls NativeBareKit.startFile(handle, filename, args) and whether the
  // native side prepends the filename to Bare.argv is decided in native code we cannot see from JS.
  // lib/node.js already works around this by reading the LAST argv; tags make the loader immune to it
  // in both directions.
  const a = loaderArgs(argv('/some/loader.bundle', '--bundle=/s/bundles/abc.bundle', '--addons=/s/bundles/dd.addons', '/storage'))
  t.is(a.bundle, '/s/bundles/abc.bundle')
  t.is(a.addons, '/s/bundles/dd.addons')
})

test('storage stays LAST so every bundle, old or new, still finds it', (t) => {
  // lib/node.js reads Bare.argv[Bare.argv.length - 1]. A bundle staged before the loader existed does
  // the same, so it keeps working unchanged under the loader — which is what makes this safe to ship
  // to devices holding older staged bundles.
  const args = ['--bundle=/s/abc.bundle', '--addons=/s/dd.addons', '/data/user/0/app/files']
  t.is(args[args.length - 1], '/data/user/0/app/files', 'storage is last')
  t.is(loaderArgs(argv(...args)).bundle, '/s/abc.bundle', 'and the tags still parse')
})

test('no --addons= is null, not a crash', (t) => {
  const a = loaderArgs(argv('--bundle=/s/abc.bundle', '/storage'))
  t.is(a.bundle, '/s/abc.bundle')
  t.is(a.addons, null)
})

test('no --bundle= is a THROW, never a guess', (t) => {
  // There is no safe default. Guessing a path here would run something nobody chose, on the one code
  // path that decides what executes on the device.
  t.exception(() => loaderArgs(argv('/storage')), /no --bundle=/)
})

test('addons are preloaded BEFORE the bundle is loaded', (t) => {
  const order = []
  runLoader({
    argv: argv('--bundle=/s/abc.bundle', '--addons=/s/dd.addons', '/storage'),
    preload: ({ dir }) => { order.push('preload:' + dir); return 3 },
    load: (p) => { order.push('load:' + p) }
  })
  t.alike(order, ['preload:/s/dd.addons', 'load:/s/abc.bundle'], 'preload first, then load')
})

test('a release with no addons still loads its bundle', (t) => {
  // Every release published so far. The loader runs on EVERY boot, including the APK's own bundle.
  const order = []
  runLoader({
    argv: argv('--bundle=/s/worklet.bundle', '/storage'),
    preload: ({ dir }) => { order.push('preload:' + String(dir)); return 0 },
    load: (p) => { order.push('load:' + p) }
  })
  t.alike(order, ['preload:null', 'load:/s/worklet.bundle'])
})

test('a bundle that throws is NOT caught — the watchdog must see it', (t) => {
  // ⛔ NO FALLBACK HERE, DELIBERATELY. worklet.js writes pending.json before starting and clears it on
  // ready; a marker that survives a boot condemns that bundle. If the loader quietly ran the APK's
  // bundle instead, the node WOULD reach ready, pending.json would be cleared, and the broken bundle
  // would never be condemned — it would be chosen again on every boot, for ever. Failing loudly is what
  // makes the existing watchdog work.
  t.exception(() => runLoader({
    argv: argv('--bundle=/s/abc.bundle', '/storage'),
    preload: () => 0,
    load: () => { throw new Error('bundle is corrupt') }
  }), /bundle is corrupt/)
})

// ── the in-bundle bootstrap ───────────────────────────────────────────────────────────────────────
//
// ⛔ THE APK LOADER IS NOT ENOUGH, AND THAT WAS THE WHOLE POINT. A preloader that only ships inside the
// APK means gaining the capability costs a sideload — exactly the cost this work exists to remove. It
// has to be possible to ship the preloader ITSELF as an ordinary JS release over the OTA that already
// works today.
//
// It is, because `new Addon(fileURL)` needs no filesystem module to dlopen a path, and a module inside
// a bundle knows where its bundle is: import.meta.url is `file:///…/<hash>.bundle/lib/bootstrap.js`.
// Measured on a real packed bundle, not assumed. So a staged bundle can find the addons staged beside
// it with no help from the shell — which is what makes this deliverable to the APK already on a device.
import { bundlePathFromUrl, addonsDirFor } from '../lib/host/loader-core.js'

test('a module inside a bundle can find its own bundle file', (t) => {
  t.is(
    bundlePathFromUrl('file:///data/user/0/app/files/bundles/abc123.bundle/lib/bootstrap.js'),
    '/data/user/0/app/files/bundles/abc123.bundle'
  )
  t.is(
    bundlePathFromUrl('file:///d/b/x.bundle/node_modules/deep/nested/mod.js'),
    '/d/b/x.bundle',
    'however deep the module sits'
  )
})

test('running from SOURCE is not a bundle, and says so', (t) => {
  // The test suite itself, and any laptop run. Null rather than a guess: preloading from a directory
  // invented out of a source path would be a silent no-op at best.
  t.is(bundlePathFromUrl('file:///repo/favela-surface-android/lib/bootstrap.js'), null)
  t.is(bundlePathFromUrl(''), null)
  t.is(bundlePathFromUrl(null), null)
})

test('a path with .bundle inside a DIRECTORY name is not mistaken for the bundle', (t) => {
  t.is(bundlePathFromUrl('file:///x/my.bundles/lib/a.js'), null, 'my.bundles is not a bundle')
  t.is(bundlePathFromUrl('file:///x/a.bundle.old/lib/a.js'), null, 'a.bundle.old is not a bundle')
})

test('the addon directory is derived from the bundle FILE, so nothing has to be told', (t) => {
  t.is(
    addonsDirFor('file:///data/user/0/app/files/bundles/abc123.bundle/lib/bootstrap.js'),
    '/data/user/0/app/files/bundles/abc123.bundle.addons'
  )
  t.is(addonsDirFor('file:///repo/lib/bootstrap.js'), null, 'no bundle, no addons')
})