summaryrefslogtreecommitdiffstats
path: root/toolkit/components/backgroundtasks/BackgroundTasksTestUtils.sys.mjs
blob: 72a9d3e4f3441785b3e99374557daf87ba5f2904 (plain)
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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { Subprocess } from "resource://gre/modules/Subprocess.sys.mjs";

function getFirefoxExecutableFilename() {
  if (AppConstants.platform === "win") {
    return AppConstants.MOZ_APP_NAME + ".exe";
  }
  if (AppConstants.platform == "linux") {
    return AppConstants.MOZ_APP_NAME + "-bin";
  }
  return AppConstants.MOZ_APP_NAME;
}

// Returns a nsIFile to the firefox.exe (really, application) executable file.
function getFirefoxExecutableFile() {
  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  file = Services.dirsvc.get("GreBinD", Ci.nsIFile);

  file.append(getFirefoxExecutableFilename());
  return file;
}

export var BackgroundTasksTestUtils = {
  init(scope) {
    this.testScope = scope;
  },

  async do_backgroundtask(
    task,
    options = { extraArgs: [], extraEnv: {}, onStdoutLine: null }
  ) {
    options = Object.assign({}, options);
    options.extraArgs = options.extraArgs || [];
    options.extraEnv = options.extraEnv || {};

    let command = getFirefoxExecutableFile().path;
    let args = ["--backgroundtask", task];
    args.push(...options.extraArgs);

    // Ensure `resource://testing-common` gets mapped.
    let protocolHandler = Services.io
      .getProtocolHandler("resource")
      .QueryInterface(Ci.nsIResProtocolHandler);

    let uri = protocolHandler.getSubstitution("testing-common");
    const { Assert } = this.testScope;
    Assert.ok(!!uri, "resource://testing-common is not substituted");

    // The equivalent of _TESTING_MODULES_DIR in xpcshell.
    options.extraEnv.XPCSHELL_TESTING_MODULES_URI = uri.spec;

    // Now we can actually invoke the process.
    console.info(`launching background task`, {
      command,
      args,
      extraEnv: options.extraEnv,
    });
    let { proc, readPromise } = await Subprocess.call({
      command,
      arguments: args,
      environment: options.extraEnv,
      environmentAppend: true,
      stderr: "stdout",
    }).then(p => {
      p.stdin.close().catch(() => {
        // It's possible that the process exists before we close stdin.
        // In that case, we should ignore the errors.
      });
      const dumpPipe = async pipe => {
        // We must assemble all of the string fragments from stdout.
        let leftover = "";
        let data = await pipe.readString();
        while (data) {
          data = leftover + data;
          // When the string is empty and the separator is not empty,
          // split() returns an array containing one empty string,
          // rather than an empty array, i.e., we always have
          // `lines.length > 0`.
          let lines = data.split(/\r\n|\r|\n/);
          for (let line of lines.slice(0, -1)) {
            dump(`${p.pid}> ${line}\n`);
            if (options.onStdoutLine) {
              options.onStdoutLine(line, p);
            }
          }
          leftover = lines[lines.length - 1];
          data = await pipe.readString();
        }

        if (leftover.length) {
          dump(`${p.pid}> ${leftover}\n`);
          if (options.onStdoutLine) {
            options.onStdoutLine(leftover, p);
          }
        }
      };
      let readPromise = dumpPipe(p.stdout);

      return { proc: p, readPromise };
    });

    let { exitCode } = await proc.wait();
    try {
      // Read from the output pipe.
      await readPromise;
    } catch (e) {
      if (e.message !== "File closed") {
        throw e;
      }
    }

    return exitCode;
  },

  // Setup that allows to use the profile service in xpcshell tests,
  // lifted from `toolkit/profile/xpcshell/head.js`.
  setupProfileService() {
    let gProfD = this.testScope.do_get_profile();
    let gDataHome = gProfD.clone();
    gDataHome.append("data");
    gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
    let gDataHomeLocal = gProfD.clone();
    gDataHomeLocal.append("local");
    gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);

    let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
      Ci.nsIXREDirProvider
    );
    xreDirProvider.setUserDataDirectory(gDataHome, false);
    xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
  },
};