diff options
Diffstat (limited to 'test/fixtures/mock-npm.js')
-rw-r--r-- | test/fixtures/mock-npm.js | 190 |
1 files changed, 115 insertions, 75 deletions
diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js index a51ec3e5b..751885531 100644 --- a/test/fixtures/mock-npm.js +++ b/test/fixtures/mock-npm.js @@ -1,71 +1,126 @@ -const npmlog = require('npmlog') -const procLog = require('../../lib/utils/proc-log-listener.js') -procLog.reset() - -// In theory we shouldn't have to do this if all the tests were tearing down -// their listeners properly, we're still getting warnings even though -// perfStop() and procLog.reset() is in the teardown script. This silences the -// warnings for now -require('events').defaultMaxListeners = Infinity - -const realLog = {} -for (const level in npmlog.levels) { - realLog[level] = npmlog[level] -} - -const { title, execPath } = process +const os = require('os') +const fs = require('fs').promises +const path = require('path') +const mockLogs = require('./mock-logs') +const mockGlobals = require('./mock-globals') +const log = require('../../lib/utils/log-shim') -// Eventually this should default to having a prefix of an empty testdir, and -// awaiting npm.load() unless told not to (for npm tests for example). Ideally -// the prefix of an empty dir is inferred rather than explicitly set const RealMockNpm = (t, otherMocks = {}) => { - const mock = {} - mock.logs = [] - mock.outputs = [] - mock.joinedOutput = () => { - return mock.outputs.map(o => o.join(' ')).join('\n') + const mock = { + ...mockLogs(otherMocks), + outputs: [], + joinedOutput: () => mock.outputs.map(o => o.join(' ')).join('\n'), } - mock.filteredLogs = title => mock.logs.filter(([t]) => t === title).map(([, , msg]) => msg) - const Npm = t.mock('../../lib/npm.js', otherMocks) - class MockNpm extends Npm { - constructor () { - super() - for (const level in npmlog.levels) { - npmlog[level] = (...msg) => { - mock.logs.push([level, ...msg]) - - const l = npmlog.level - npmlog.level = 'silent' - realLog[level](...msg) - npmlog.level = l - } - } - // npm.js tests need this restored to actually test this function! - mock.npmOutput = this.output - this.output = (...msg) => mock.outputs.push(msg) + + const Npm = t.mock('../../lib/npm.js', { + ...otherMocks, + ...mock.logMocks, + }) + + mock.Npm = class MockNpm extends Npm { + // lib/npm.js tests needs this to actually test the function! + originalOutput (...args) { + super.output(...args) + } + + output (...args) { + mock.outputs.push(args) } } - mock.Npm = MockNpm - t.afterEach(() => { - mock.outputs.length = 0 - mock.logs.length = 0 + + return mock +} + +// Resolve some options to a function call with supplied args +const result = (fn, ...args) => typeof fn === 'function' ? fn(...args) : fn + +const LoadMockNpm = async (t, { + init = true, + load = init, + testdir = {}, + config = {}, + mocks = {}, + globals = null, +} = {}) => { + // Mock some globals with their original values so they get torn down + // back to the original at the end of the test since they are manipulated + // by npm itself + mockGlobals(t, { + process: { + title: process.title, + execPath: process.execPath, + env: { + npm_command: process.env.npm_command, + COLOR: process.env.COLOR, + }, + }, }) - t.teardown(() => { - process.removeAllListeners('time') - process.removeAllListeners('timeEnd') - npmlog.record.length = 0 - for (const level in npmlog.levels) { - npmlog[level] = realLog[level] - } - procLog.reset() - process.title = title - process.execPath = execPath - delete process.env.npm_command - delete process.env.COLOR + const { Npm, ...rest } = RealMockNpm(t, mocks) + + if (!init && load) { + throw new Error('cant `load` without `init`') + } + + const _level = log.level + t.teardown(() => log.level = _level) + + if (config.loglevel) { + // Set log level as early as possible since it is set + // on the npmlog singleton and shared across everything + log.level = config.loglevel + } + + const dir = t.testdir({ root: testdir, cache: {} }) + const prefix = path.join(dir, 'root') + const cache = path.join(dir, 'cache') + + // Set cache to testdir via env var so it is available when load is run + // XXX: remove this for a solution where cache argv is passed in + mockGlobals(t, { + 'process.env.npm_config_cache': cache, }) - return mock + if (globals) { + mockGlobals(t, result(globals, { prefix, cache })) + } + + const npm = init ? new Npm() : null + t.teardown(() => npm && npm.unload()) + + if (load) { + await npm.load() + for (const [k, v] of Object.entries(result(config, { npm, prefix, cache }))) { + npm.config.set(k, v) + } + if (config.loglevel) { + // Set global loglevel *again* since it possibly got reset during load + // XXX: remove with npmlog + log.level = config.loglevel + } + npm.prefix = prefix + npm.cache = cache + } + + return { + ...rest, + Npm, + npm, + prefix, + cache, + debugFile: async () => { + const readFiles = npm.logFiles.map(f => fs.readFile(f)) + const logFiles = await Promise.all(readFiles) + return logFiles + .flatMap((d) => d.toString().trim().split(os.EOL)) + .filter(Boolean) + .join('\n') + }, + timingFile: async () => { + const data = await fs.readFile(path.resolve(cache, '_timing.json'), 'utf8') + return JSON.parse(data) // XXX: this fails if multiple timings are written + }, + } } const realConfig = require('../../lib/utils/config') @@ -96,21 +151,6 @@ class MockNpm { set: (k, v) => config[k] = v, list: [{ ...realConfig.defaults, ...config }], } - if (!this.log) { - this.log = { - clearProgress: () => {}, - disableProgress: () => {}, - enableProgress: () => {}, - http: () => {}, - info: () => {}, - levels: [], - notice: () => {}, - pause: () => {}, - silly: () => {}, - verbose: () => {}, - warn: () => {}, - } - } } output (...msg) { @@ -127,5 +167,5 @@ const FakeMockNpm = (base = {}) => { module.exports = { fake: FakeMockNpm, - real: RealMockNpm, + load: LoadMockNpm, } |