OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface-android / test / preload-addons.test.js

146 lines · javascript ref:
// The loader half of OTA-delivered native addons.
//
// ⛔ WHY A SEPARATE MODULE FROM THE STAGED BUNDLE. worklet.js hands bare-kit a PATH and a null source,
// so Bare loads the staged bundle as its own entry (NativeBareKit.startFile). Preload code placed
// inside that bundle is too late and, worse, circular: its own `import` of bare-fs resolves to
// `linked:libbare-fs.<staged version>.so` — the very soname the APK might not have. The loader must
// therefore be its own bundle, packed against the APK's addon set, which then registers the staged
// addons and loads the real bundle.
//
// ⛔ AND WHY THE FILENAME IS THE CONTRACT. Addon.cache is keyed by url.href and the `linked:` branch of
// Addon.resolve goes through Addon.load, so registering under `linked:<soname>` wins before any
// dlopen-by-soname. Naming each staged file after the soname it satisfies means the loader needs no
// manifest and never parses the bundle — the directory listing IS the mapping.
import test from 'brittle'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import { fileURLToPath } from 'bare-url'
import { addonHref, preloadAddons } from '../lib/host/preload-addons.js'

let seq = 0
function tmpdir (name) {
  const dir = path.join(os.tmpdir(), 'favela-preload-' + name + '-' + (++seq))
  fs.rmSync(dir, { recursive: true, force: true })
  fs.mkdirSync(dir, { recursive: true })
  return dir
}

test('a staged filename maps to the linked: URL the bundle asks for', (t) => {
  t.is(addonHref('libbare-fs.4.8.1.so'), 'linked:libbare-fs.4.8.1.so')
  t.is(addonHref('libsodium-native.5.1.0.so'), 'linked:libsodium-native.5.1.0.so')
})

test('every staged addon is registered under its linked: URL', (t) => {
  const dir = tmpdir('register')
  fs.writeFileSync(path.join(dir, 'libbare-fs.4.8.1.so'), 'x')
  fs.writeFileSync(path.join(dir, 'libsodium-native.5.1.0.so'), 'x')

  const cache = Object.create(null)
  const loaded = []
  const Addon = function (url) { loaded.push(url.href); this.url = url }
  const n = preloadAddons({ dir, cache, Addon })

  t.is(n, 2, 'both registered')
  t.ok(cache['linked:libbare-fs.4.8.1.so'], 'bare-fs is in the cache under its soname')
  t.ok(cache['linked:libsodium-native.5.1.0.so'], 'sodium-native too')
  t.ok(loaded.every((h) => h.startsWith('file://')), 'each was loaded from an absolute file: path')
  t.ok(loaded.some((h) => h.endsWith('/libbare-fs.4.8.1.so')), 'from the staged directory')
})

test('an addon the APK already satisfies is NOT overridden if it is not staged', (t) => {
  // Staging is additive. Anything absent from the dir must fall through to dlopen-by-soname, which is
  // what makes a release able to ship ONE changed addon rather than all 58.
  const dir = tmpdir('additive')
  fs.writeFileSync(path.join(dir, 'libbare-fs.4.8.1.so'), 'x')

  const cache = Object.create(null)
  preloadAddons({ dir, cache, Addon: function (url) { this.url = url } })

  t.ok(cache['linked:libbare-fs.4.8.1.so'], 'the staged one is registered')
  t.absent(cache['linked:libsodium-native.5.1.0.so'], 'the unstaged one is left to the APK')
})

test('no addons directory is zero, not a throw', (t) => {
  // Every release published so far carries no addons at all, and the loader runs on every boot.
  const cache = Object.create(null)
  t.is(preloadAddons({ dir: null, cache, Addon: function () {} }), 0, 'null dir')
  t.is(preloadAddons({ dir: path.join(os.tmpdir(), 'does-not-exist-' + (++seq)), cache, Addon: function () {} }), 0, 'missing dir')
  t.alike(cache, Object.create(null), 'nothing registered')
})

test('a non-library file in the directory is ignored', (t) => {
  const dir = tmpdir('junk')
  fs.writeFileSync(path.join(dir, 'libbare-fs.4.8.1.so'), 'x')
  fs.writeFileSync(path.join(dir, 'README'), 'x')
  fs.writeFileSync(path.join(dir, 'libbroken.so.part'), 'x')   // an interrupted write

  const cache = Object.create(null)
  const n = preloadAddons({ dir, cache, Addon: function (url) { this.url = url } })
  t.is(n, 1, 'only the .so is taken')
  t.absent(cache['linked:README'], 'no junk in the cache')
  t.absent(cache['linked:libbroken.so.part'], 'a .part is not a library')
})

test('one addon that fails to load does not stop the others', (t) => {
  // A bundle missing ONE addon still cannot run — but the loader is not the place to decide that, and
  // dying here would take out the boot with no diagnostic. Report and continue; the failure surfaces
  // where it means something, at the require that needs it.
  const dir = tmpdir('partial')
  fs.writeFileSync(path.join(dir, 'liba.1.0.0.so'), 'x')
  fs.writeFileSync(path.join(dir, 'libb.1.0.0.so'), 'x')

  const cache = Object.create(null)
  const said = []
  const n = preloadAddons({
    dir,
    cache,
    Addon: function (url) { if (url.href.endsWith('liba.1.0.0.so')) throw new Error('bad ELF'); this.url = url },
    say: (m) => said.push(m)
  })
  t.is(n, 1, 'the good one is still registered')
  t.ok(cache['linked:libb.1.0.0.so'], 'b loaded')
  t.absent(cache['linked:liba.1.0.0.so'], 'a did not')
  t.ok(said.some((m) => m.includes('liba.1.0.0.so') && m.includes('bad ELF')), 'and it said which and why: ' + said.join(' | '))
})

test('REAL: a genuine addon loads from an arbitrary staged path', (t) => {
  // The whole route in one assertion, against Bare's real Addon rather than a double. A prebuild copied
  // under a new name is a path the linker has never seen, which is exactly the OTA case: bytes that
  // arrived over the swarm, sitting in app storage, loaded by absolute path.
  const here = path.dirname(fileURLToPath(import.meta.url))
  const src = path.join(here, '..', 'node_modules', 'bare-url', 'prebuilds', Bare.Addon.host, 'bare-url.bare')
  if (!fs.existsSync(src)) return t.pass('no prebuild for ' + Bare.Addon.host + ' — nothing to prove here')

  const dir = tmpdir('real')
  const staged = path.join(dir, 'libstaged-probe.9.9.9.so')
  fs.writeFileSync(staged, fs.readFileSync(src))
  fs.chmodSync(staged, 0o755)

  const cache = Object.create(null)
  const n = preloadAddons({ dir, cache })
  t.is(n, 1, 'loaded')
  const addon = cache['linked:libstaged-probe.9.9.9.so']
  t.ok(addon, 'registered under the soname a bundle would ask for')
  t.ok(addon.exports && typeof addon.exports === 'object', 'and it really initialised — it has exports')
})

test('an addon already registered is NOT loaded twice', (t) => {
  // ⛔ TWO PRELOADERS CAN RUN IN ONE PROCESS. The APK's loader registers staged addons before starting
  // the bundle, and the bundle's own bootstrap then does the same from the same directory — an APK new
  // enough to have the loader running a bundle new enough to bootstrap itself. A second
  // `new Addon(sameUrl)` would dlopen the same file and re-run its init, so the first registration wins
  // and the second is skipped. Addon.cache is keyed by href, which makes that check exact.
  const dir = tmpdir('twice')
  fs.writeFileSync(path.join(dir, 'libbare-fs.4.8.1.so'), 'x')

  let constructed = 0
  const Addon = function (url) { constructed++; this.url = url }
  const cache = Object.create(null)

  t.is(preloadAddons({ dir, cache, Addon }), 1, 'first pass registers it')
  t.is(constructed, 1)
  t.is(preloadAddons({ dir, cache, Addon }), 0, 'second pass registers nothing new')
  t.is(constructed, 1, 'and never constructs a second Addon for the same soname')
})