Skip to content

Commit

Permalink
add exhaustive tests for ANSI-C quoted strings
Browse files Browse the repository at this point in the history
  • Loading branch information
verhovsky committed Apr 7, 2021
1 parent 73ef3f9 commit c698d28
Showing 1 changed file with 99 additions and 1 deletion.
100 changes: 99 additions & 1 deletion test/string-utils.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* global describe, it */
/* global describe, it, xit */

const { strictEqual } = require('assert')
const util = require('util')
const exec = util.promisify(require('child_process').exec)
const {
camelCase,
decamelize,
Expand Down Expand Up @@ -44,5 +46,101 @@ describe('string-utils', function () {
const testCase = "$'\\c\\ '"
await check(testCase)
})

async function check (ansiString, encoding = 'utf8') {
const result = parseAnsiCQuotedString(ansiString)

const resultBuffer = Buffer.from(result, encoding)
const firstNull = resultBuffer.indexOf(0)
const resultUpToFirstNull = firstNull === -1 ? resultBuffer : resultBuffer.slice(0, firstNull)

const { stdout } = await exec(
'printf "%s" ' + ansiString,
{ shell: '/bin/bash', encoding: 'buffer' }
)

if (!resultUpToFirstNull.equals(stdout)) {
console.log(JSON.stringify(ansiString), ansiString[4], ansiString.codePointAt(4))
console.log(resultUpToFirstNull, 'should be', stdout)
if (!resultUpToFirstNull.equals(resultBuffer)) {
console.log('(', resultUpToFirstNull, 'is actually', resultBuffer, ')')
}
console.log()
}

resultUpToFirstNull.should.deep.equal(stdout)
}

async function checkAll (prefix, value, maxLength, encoding = 'utf8') {
await check("$'\\" + prefix + value + "'", encoding)
while (value.length < maxLength) {
value = '0' + value
await check("$'\\" + prefix + value + "'", encoding)
if (value.toLowerCase() !== value) {
await check("$'\\" + prefix + value.toLowerCase() + "'", encoding)
}
}
}

// Skip exhaustive testing because it takes a long time
xit('parses all octal codes in ANSI-C quoted strings like bash', async () => {
for (let i = 0; i <= 0o777; i++) {
if (i % 256 === 0) { // null character
continue
}
await checkAll('', i.toString(8), 3, 'ascii')
}
}).timeout(5000)

xit('parses all hex codes in ANSI-C quoted strings like bash', async () => {
for (let i = 1; i <= 0xFF; i++) { // start at 1 to skip null
await checkAll('x', i.toString(16), 2, 'ascii')
}

for (let i = 1; i <= 0xFFFF; i++) {
// The high and low surrogates "\ud800" to "\udfff" fail this test
// because when the resulting string is encoded to a Buffer with UTF-8,
// it becomes a "?" replacement character, but the underlying data is correct.
// https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF
if (i >= 0xD800 && i <= 0xDFFF) {
continue
}
await checkAll('u', i.toString(16), 4)
}

for (let i = 1; i <= 0x10FFFF; i++) {
if (i >= 0xD800 && i <= 0xDFFF) {
continue
}
await checkAll('U', i.toString(16), 8)
}

// TODO: echo -n $'\U110000' produces f4 90 80 80 but String.fromCodePoint(0x110000)
// raises an error in node: Uncaught RangeError: Invalid code point 1114112
}).timeout(5 * 60 * 60 * 1000)

xit('parses control codes in ANSI-C quoted strings like bash', async () => {
// We skip \x01 and \x7F because those don't make sense as control codes
// and because bash parses them differently.
for (let i = 2; i < 0x7F; i++) {
const chr = String.fromCodePoint(i)
if (chr === "'" || chr === '\\') {
continue
}
await check("$'\\c" + chr + "'")
}
}).timeout(60 * 1000)

// We skip this because we do not handle non-ASCII characters after a control code,
// unlike bash.
xit('parses all non-ASCII codes in ANSI-C quoted strings like bash', async () => {
for (let i = 0x80; i <= 0x10FFFF; i++) {
const chr = String.fromCodePoint(i)
if (chr === "'" || chr === '\\' || (i >= 0xD800 && i <= 0xDFFF)) {
continue
}
await check("$'\\c" + chr + "'")
}
}).timeout(5 * 60 * 60 * 1000)
})
})

0 comments on commit c698d28

Please sign in to comment.