• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const log = require('../../../lib/utils/log-shim')
3const mockLogs = require('../../fixtures/mock-logs')
4const mockGlobals = require('@npmcli/mock-globals')
5const tmock = require('../../fixtures/tmock')
6const util = require('util')
7
8const mockDisplay = (t, mocks) => {
9  const { logs, logMocks } = mockLogs(mocks)
10  const Display = tmock(t, '{LIB}/utils/display', {
11    ...mocks,
12    ...logMocks,
13  })
14  const display = new Display()
15  t.teardown(() => display.off())
16  return { display, logs }
17}
18
19t.test('setup', async (t) => {
20  const { display } = mockDisplay(t)
21
22  display.load({ timing: true, loglevel: 'notice' })
23  t.equal(log.level, 'notice')
24
25  display.load({ timing: false, loglevel: 'notice' })
26  t.equal(log.level, 'notice')
27
28  display.load({ color: true })
29  t.equal(log.useColor(), true)
30
31  display.load({ unicode: true })
32  t.equal(log.gauge._theme.hasUnicode, true)
33
34  display.load({ unicode: false })
35  t.equal(log.gauge._theme.hasUnicode, false)
36
37  mockGlobals(t, { 'process.stderr.isTTY': true })
38  display.load({ progress: true })
39  t.equal(log.progressEnabled, true)
40})
41
42t.test('can log cleanly', async (t) => {
43  const explains = []
44  const { display, logs } = mockDisplay(t, {
45    npmlog: {
46      error: (...args) => logs.push(['error', ...args]),
47      warn: (...args) => logs.push(['warn', ...args]),
48    },
49    '{LIB}/utils/explain-eresolve.js': {
50      explain: (...args) => {
51        explains.push(args)
52        return 'explanation'
53      },
54    },
55  })
56
57  display.log('error', 'test\x00message')
58  t.match(logs.error, [['test^@message']])
59
60  display.log('warn', 'ERESOLVE', 'hello', { some: 'object' })
61  t.match(logs.warn, [['ERESOLVE', 'hello']])
62  t.match(explains, [[{ some: 'object' }, null, 2]])
63})
64
65t.test('handles log throwing', async (t) => {
66  const errors = []
67  mockGlobals(t, {
68    'console.error': (...args) => errors.push(args),
69  })
70  const { display } = mockDisplay(t, {
71    npmlog: {
72      verbose: () => {
73        throw new Error('verbose')
74      },
75    },
76    '{LIB}/utils/explain-eresolve.js': {
77      explain: () => {
78        throw new Error('explain')
79      },
80    },
81  })
82
83  display.log('warn', 'ERESOLVE', 'hello', { some: 'object' })
84  t.match(errors, [
85    [/attempt to log .* crashed/, Error('explain'), Error('verbose')],
86  ])
87})
88
89class CustomObj {
90  [util.inspect.custom] () {
91    return this.inspected
92  }
93}
94
95t.test('Display.clean', async (t) => {
96  const Display = require('../../../lib/utils/display')
97  const customNaN = new CustomObj()
98  const customNull = new CustomObj()
99  const customNumber = new CustomObj()
100  const customObject = new CustomObj()
101  const customString = new CustomObj()
102  const customUndefined = new CustomObj()
103  customNaN.inspected = NaN
104  customNull.inspected = null
105  customNumber.inspected = 477
106  customObject.inspected = { custom: 'rend\x00ering' }
107  customString.inspected = 'custom\x00rendering'
108  customUndefined.inspected = undefined
109  t.test('strings', async (t) => {
110    const tests = [
111      [477, '477'],
112      [null, 'null'],
113      [NaN, 'NaN'],
114      [true, 'true'],
115      [undefined, 'undefined'],
116      ['��', '��'],
117      // Cover the bounds of each range and a few characters from inside each range
118      // \x00 through \x1f
119      ['hello\x00world', 'hello^@world'],
120      ['hello\x07world', 'hello^Gworld'],
121      ['hello\x1bworld', 'hello^[world'],
122      ['hello\x1eworld', 'hello^^world'],
123      ['hello\x1fworld', 'hello^_world'],
124      // \x7f is C0
125      ['hello\x7fworld', 'hello^?world'],
126      // \x80 through \x9f
127      ['hello\x80world', 'hello^@world'],
128      ['hello\x87world', 'hello^Gworld'],
129      ['hello\x9eworld', 'hello^^world'],
130      ['hello\x9fworld', 'hello^_world'],
131      // Allowed C0
132      ['hello\tworld', 'hello\tworld'],
133      ['hello\nworld', 'hello\nworld'],
134      ['hello\vworld', 'hello\vworld'],
135      ['hello\rworld', 'hello\rworld'],
136      // Allowed SGR
137      ['hello\x1b[38;5;254mworld', 'hello\x1b[38;5;254mworld'],
138      ['hello\x1b[mworld', 'hello\x1b[mworld'],
139      // Unallowed CSI / OSC
140      ['hello\x1b[2Aworld', 'hello^[[2Aworld'],
141      ['hello\x9b[2Aworld', 'hello^[[2Aworld'],
142      ['hello\x9decho goodbye\x9cworld', 'hello^]echo goodbye^\\world'],
143      // This is done twice to ensure we define inspect.custom as writable
144      [{ test: 'object' }, "{ test: 'object' }"],
145      [{ test: 'object' }, "{ test: 'object' }"],
146      // Make sure custom util.inspect doesn't bypass our cleaning
147      [customNaN, 'NaN'],
148      [customNull, 'null'],
149      [customNumber, '477'],
150      [customObject, "{ custom: 'rend\\x00ering' }"],
151      [customString, 'custom^@rendering'],
152      [customUndefined, 'undefined'],
153      // UTF-16 form of 8-bit C1
154      ['hello\xc2\x9bworld', 'hello\xc2^[world'],
155    ]
156    for (const [dirty, clean] of tests) {
157      const cleaned = Display.clean(dirty)
158      t.equal(util.format(cleaned), clean)
159    }
160  })
161})
162