mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 09:23:52 -05:00
The compiler output pane only links error messages to the source editor when a filename is given. `parseRustOutput` didn’t parse the filename, so Rust error messages were never linked. This PR also simplifies the regex used to parse the `--> filename:line:column` line in the `rustc` output. As far as I’m aware (and I checked the history of the corresponding output in the `rustc` source), the output format will always be `--> filename:line:column` and not anything else accepted by the previous regex (no parentheses around the line number, the column is not optional).
772 lines
29 KiB
TypeScript
772 lines
29 KiB
TypeScript
// Copyright (c) 2017, Compiler Explorer Authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
|
|
import {fileURLToPath} from 'node:url';
|
|
|
|
import {describe, expect, it} from 'vitest';
|
|
import winston from 'winston';
|
|
|
|
import {makeLogStream} from '../lib/logger.js';
|
|
import * as utils from '../lib/utils.js';
|
|
import {newTempDir} from './utils.js';
|
|
|
|
describe('Splits lines', () => {
|
|
it('handles empty input', () => {
|
|
expect(utils.splitLines('')).toEqual([]);
|
|
});
|
|
it('handles a single line with no newline', () => {
|
|
expect(utils.splitLines('A line')).toEqual(['A line']);
|
|
});
|
|
it('handles a single line with a newline', () => {
|
|
expect(utils.splitLines('A line\n')).toEqual(['A line']);
|
|
});
|
|
it('handles multiple lines', () => {
|
|
expect(utils.splitLines('A line\nAnother line\n')).toEqual(['A line', 'Another line']);
|
|
});
|
|
it('handles multiple lines ending on a non-newline', () => {
|
|
expect(utils.splitLines('A line\nAnother line\nLast line')).toEqual(['A line', 'Another line', 'Last line']);
|
|
});
|
|
it('handles empty lines', () => {
|
|
expect(utils.splitLines('A line\n\nA line after an empty')).toEqual(['A line', '', 'A line after an empty']);
|
|
});
|
|
it('handles a single empty line', () => {
|
|
expect(utils.splitLines('\n')).toEqual(['']);
|
|
});
|
|
it('handles multiple empty lines', () => {
|
|
expect(utils.splitLines('\n\n\n')).toEqual(['', '', '']);
|
|
});
|
|
it('handles \\r\\n lines', () => {
|
|
expect(utils.splitLines('Some\r\nLines\r\n')).toEqual(['Some', 'Lines']);
|
|
});
|
|
});
|
|
|
|
describe('Expands tabs', () => {
|
|
it('leaves non-tabs alone', () => {
|
|
expect(utils.expandTabs('This has no tabs at all')).toEqual('This has no tabs at all');
|
|
});
|
|
it('at beginning of line', () => {
|
|
expect(utils.expandTabs('\tOne tab')).toEqual(' One tab');
|
|
expect(utils.expandTabs('\t\tTwo tabs')).toEqual(' Two tabs');
|
|
});
|
|
it('mid-line', () => {
|
|
expect(utils.expandTabs('0\t1234567A')).toEqual('0 1234567A');
|
|
expect(utils.expandTabs('01\t234567A')).toEqual('01 234567A');
|
|
expect(utils.expandTabs('012\t34567A')).toEqual('012 34567A');
|
|
expect(utils.expandTabs('0123\t4567A')).toEqual('0123 4567A');
|
|
expect(utils.expandTabs('01234\t567A')).toEqual('01234 567A');
|
|
expect(utils.expandTabs('012345\t67A')).toEqual('012345 67A');
|
|
expect(utils.expandTabs('0123456\t7A')).toEqual('0123456 7A');
|
|
expect(utils.expandTabs('01234567\tA')).toEqual('01234567 A');
|
|
});
|
|
});
|
|
|
|
describe('Parses compiler output', () => {
|
|
it('handles simple cases', () => {
|
|
expect(utils.parseOutput('Line one\nLine two', 'bob.cpp')).toEqual([{text: 'Line one'}, {text: 'Line two'}]);
|
|
expect(utils.parseOutput('Line one\nbob.cpp:1 Line two', 'bob.cpp')).toEqual([
|
|
{text: 'Line one'},
|
|
{
|
|
tag: {column: 0, line: 1, text: 'Line two', severity: 3, file: 'bob.cpp'},
|
|
text: '<source>:1 Line two',
|
|
},
|
|
]);
|
|
expect(utils.parseOutput('Line one\nbob.cpp:1:5: Line two', 'bob.cpp')).toEqual([
|
|
{text: 'Line one'},
|
|
{
|
|
tag: {column: 5, line: 1, text: 'Line two', severity: 3, file: 'bob.cpp'},
|
|
text: '<source>:1:5: Line two',
|
|
},
|
|
]);
|
|
});
|
|
it('handles windows output', () => {
|
|
expect(utils.parseOutput('bob.cpp(1) Oh noes', 'bob.cpp')).toEqual([
|
|
{
|
|
tag: {column: 0, line: 1, text: 'Oh noes', severity: 3, file: 'bob.cpp'},
|
|
text: '<source>(1) Oh noes',
|
|
},
|
|
]);
|
|
});
|
|
it('replaces all references to input source', () => {
|
|
expect(utils.parseOutput('bob.cpp:1 error in bob.cpp', 'bob.cpp')).toEqual([
|
|
{
|
|
tag: {column: 0, line: 1, text: 'error in <source>', severity: 3, file: 'bob.cpp'},
|
|
text: '<source>:1 error in <source>',
|
|
},
|
|
]);
|
|
});
|
|
it('treats warnings and notes as the correct severity', () => {
|
|
expect(utils.parseOutput('Line one\nbob.cpp:1:5: warning Line two', 'bob.cpp')).toEqual([
|
|
{text: 'Line one'},
|
|
{
|
|
tag: {column: 5, line: 1, text: 'warning Line two', severity: 2, file: 'bob.cpp'},
|
|
text: '<source>:1:5: warning Line two',
|
|
},
|
|
]);
|
|
expect(utils.parseOutput('Line one\nbob.cpp:1:5: note Line two', 'bob.cpp')).toEqual([
|
|
{text: 'Line one'},
|
|
{
|
|
tag: {column: 5, line: 1, text: 'note Line two', severity: 1, file: 'bob.cpp'},
|
|
text: '<source>:1:5: note Line two',
|
|
},
|
|
]);
|
|
});
|
|
it('treats <stdin> as if it were the compiler source', () => {
|
|
expect(
|
|
utils.parseOutput("<stdin>:120:25: error: variable or field 'transform_data' declared void", 'bob.cpp'),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
column: 25,
|
|
line: 120,
|
|
text: "error: variable or field 'transform_data' declared void",
|
|
severity: 3,
|
|
file: 'bob.cpp',
|
|
},
|
|
text: "<source>:120:25: error: variable or field 'transform_data' declared void",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parser error with full path', () => {
|
|
expect(utils.parseOutput("/app/example.cl:5:30: error: use of undeclared identifier 'ad'")).toEqual([
|
|
{
|
|
tag: {
|
|
file: 'example.cl',
|
|
column: 30,
|
|
line: 5,
|
|
text: "error: use of undeclared identifier 'ad'",
|
|
severity: 3,
|
|
},
|
|
text: "example.cl:5:30: error: use of undeclared identifier 'ad'",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('removes hyperlink escape sequences', () => {
|
|
expect(
|
|
utils.parseOutput(
|
|
't.c:3:1: warning: control reaches end of non-void function [\x1B]8;;https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Warning-Options.html#index-Wno-return-type\x1B\\-Wreturn-type\x1B]8;;\x1B\\]',
|
|
),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
file: 't.c',
|
|
line: 3,
|
|
column: 1,
|
|
text: 'warning: control reaches end of non-void function [-Wreturn-type]',
|
|
severity: 2,
|
|
},
|
|
text: 't.c:3:1: warning: control reaches end of non-void function [\x1B]8;;https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Warning-Options.html#index-Wno-return-type\x1B\\-Wreturn-type\x1B]8;;\x1B\\]',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Pascal compiler output', () => {
|
|
it('recognize fpc identifier not found error', () => {
|
|
expect(utils.parseOutput('output.pas(13,23) Error: Identifier not found "adsadasd"', 'output.pas')).toEqual([
|
|
{
|
|
tag: {
|
|
column: 23,
|
|
line: 13,
|
|
text: 'Error: Identifier not found "adsadasd"',
|
|
severity: 3,
|
|
file: 'output.pas',
|
|
},
|
|
text: '<source>(13,23) Error: Identifier not found "adsadasd"',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('recognize fpc exiting error', () => {
|
|
expect(
|
|
utils.parseOutput('output.pas(17) Fatal: There were 1 errors compiling module, stopping', 'output.pas'),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
column: 0,
|
|
line: 17,
|
|
text: 'Fatal: There were 1 errors compiling module, stopping',
|
|
severity: 3,
|
|
file: 'output.pas',
|
|
},
|
|
text: '<source>(17) Fatal: There were 1 errors compiling module, stopping',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('removes the temp path', () => {
|
|
expect(
|
|
utils.parseOutput(
|
|
'Compiling /tmp/path/prog.dpr\noutput.pas(17) Fatal: There were 1 errors compiling module, stopping',
|
|
'output.pas',
|
|
'/tmp/path/',
|
|
),
|
|
).toEqual([
|
|
{
|
|
text: 'Compiling prog.dpr',
|
|
},
|
|
{
|
|
tag: {
|
|
column: 0,
|
|
line: 17,
|
|
text: 'Fatal: There were 1 errors compiling module, stopping',
|
|
severity: 3,
|
|
file: 'output.pas',
|
|
},
|
|
text: '<source>(17) Fatal: There were 1 errors compiling module, stopping',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Rust compiler output', () => {
|
|
it('handles simple cases', () => {
|
|
expect(utils.parseRustOutput('Line one\nLine two', 'bob.rs')).toEqual([{text: 'Line one'}, {text: 'Line two'}]);
|
|
expect(utils.parseRustOutput('Unrelated\nLine one\n --> bob.rs:1:0\nUnrelated', 'bob.rs')).toEqual([
|
|
{text: 'Unrelated'},
|
|
{
|
|
tag: {file: 'bob.rs', column: 0, line: 1, text: 'Line one', severity: 3, fixes: []},
|
|
text: 'Line one',
|
|
},
|
|
{
|
|
tag: {file: 'bob.rs', column: 0, line: 1, text: '', severity: 3},
|
|
text: ' --> <source>:1:0',
|
|
},
|
|
{text: 'Unrelated'},
|
|
]);
|
|
expect(utils.parseRustOutput('Line one\n --> bob.rs:1:5', 'bob.rs')).toEqual([
|
|
{
|
|
tag: {file: 'bob.rs', column: 5, line: 1, text: 'Line one', severity: 3, fixes: []},
|
|
text: 'Line one',
|
|
},
|
|
{
|
|
tag: {file: 'bob.rs', column: 5, line: 1, text: '', severity: 3},
|
|
text: ' --> <source>:1:5',
|
|
},
|
|
]);
|
|
expect(utils.parseRustOutput('Multiple spaces\n --> bob.rs:1:5', 'bob.rs')).toEqual([
|
|
{
|
|
tag: {file: 'bob.rs', column: 5, line: 1, text: 'Multiple spaces', severity: 3, fixes: []},
|
|
text: 'Multiple spaces',
|
|
},
|
|
{
|
|
tag: {file: 'bob.rs', column: 5, line: 1, text: '', severity: 3},
|
|
text: ' --> <source>:1:5',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('replaces all references to input source', () => {
|
|
expect(utils.parseRustOutput('error: Error in bob.rs\n --> bob.rs:1:42', 'bob.rs')).toEqual([
|
|
{
|
|
tag: {file: 'bob.rs', column: 42, line: 1, text: 'error: Error in <source>', severity: 3, fixes: []},
|
|
text: 'error: Error in <source>',
|
|
},
|
|
{
|
|
tag: {file: 'bob.rs', column: 42, line: 1, text: '', severity: 3},
|
|
text: ' --> <source>:1:42',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('treats <stdin> as if it were the compiler source', () => {
|
|
expect(utils.parseRustOutput('error: <stdin> is sad\n --> <stdin>:120:25', 'bob.rs')).toEqual([
|
|
{
|
|
tag: {file: 'bob.rs', column: 25, line: 120, text: 'error: <source> is sad', severity: 3, fixes: []},
|
|
text: 'error: <source> is sad',
|
|
},
|
|
{
|
|
tag: {file: 'bob.rs', column: 25, line: 120, text: '', severity: 3},
|
|
text: ' --> <source>:120:25',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('removes hyperlink escape sequences', () => {
|
|
expect(
|
|
utils.parseRustOutput(
|
|
'error[\x1B]8;;https://doc.rust-lang.org/error_codes/E0425.html\x07E0425\x1B]8;;\x07]: cannot find value `x` in this scope\n --> <source>:42:27',
|
|
),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: 'error[E0425]: cannot find value `x` in this scope',
|
|
severity: 3,
|
|
fixes: [],
|
|
},
|
|
text: 'error[\x1B]8;;https://doc.rust-lang.org/error_codes/E0425.html\x07E0425\x1B]8;;\x07]: cannot find value `x` in this scope',
|
|
},
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: '',
|
|
severity: 3,
|
|
},
|
|
text: ' --> <source>:42:27',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('emits quickfixes', () => {
|
|
expect(utils.parseRustOutput('error\n --> <source>:42:27\n15 + use std::collections::HashMap;')).toEqual([
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: 'error',
|
|
severity: 3,
|
|
fixes: [
|
|
{
|
|
title: 'Add import for `std::collections::HashMap`',
|
|
edits: [
|
|
{
|
|
line: 15,
|
|
column: 1,
|
|
endline: 15,
|
|
endcolumn: 1,
|
|
text: 'use std::collections::HashMap;\n',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
text: 'error',
|
|
},
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: '',
|
|
severity: 3,
|
|
},
|
|
text: ' --> <source>:42:27',
|
|
},
|
|
{text: '15 + use std::collections::HashMap;'},
|
|
]);
|
|
|
|
expect(
|
|
utils.parseRustOutput(
|
|
'error\n --> <source>:42:27\n = help: add `#![feature(num_midpoint_signed)]` to the crate attributes to enable',
|
|
),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: 'error',
|
|
severity: 3,
|
|
fixes: [
|
|
{
|
|
title: 'Add feature flag `num_midpoint_signed`',
|
|
edits: [
|
|
{
|
|
line: 1,
|
|
column: 1,
|
|
endline: 1,
|
|
endcolumn: 1,
|
|
text: '#![feature(num_midpoint_signed)]\n',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
text: 'error',
|
|
},
|
|
{
|
|
tag: {
|
|
line: 42,
|
|
column: 27,
|
|
text: '',
|
|
severity: 3,
|
|
},
|
|
text: ' --> <source>:42:27',
|
|
},
|
|
{text: ' = help: add `#![feature(num_midpoint_signed)]` to the crate attributes to enable'},
|
|
]);
|
|
});
|
|
|
|
it('attaches filenames to errors from multiple files', () => {
|
|
// https://godbolt.org/z/nWE3PTeGf
|
|
expect(
|
|
utils.parseRustOutput(
|
|
`warning: function \`f1\` is never used
|
|
--> <source>:3:4
|
|
|
|
warning: function \`f2\` is never used
|
|
--> /app/m.rs:1:4
|
|
|
|
warning: 2 warnings emitted`,
|
|
'example.rs',
|
|
),
|
|
).toEqual([
|
|
{
|
|
text: 'warning: function `f1` is never used',
|
|
tag: {
|
|
line: 3,
|
|
column: 4,
|
|
file: 'example.rs',
|
|
severity: 2,
|
|
text: 'warning: function `f1` is never used',
|
|
fixes: [],
|
|
},
|
|
},
|
|
{
|
|
text: ' --> <source>:3:4',
|
|
tag: {
|
|
line: 3,
|
|
column: 4,
|
|
file: 'example.rs',
|
|
severity: 3,
|
|
text: '',
|
|
},
|
|
},
|
|
{text: ''},
|
|
{
|
|
text: 'warning: function `f2` is never used',
|
|
tag: {
|
|
line: 1,
|
|
column: 4,
|
|
file: 'm.rs',
|
|
severity: 2,
|
|
text: 'warning: function `f2` is never used',
|
|
fixes: [],
|
|
},
|
|
},
|
|
{
|
|
text: ' --> /app/m.rs:1:4',
|
|
tag: {
|
|
line: 1,
|
|
column: 4,
|
|
file: 'm.rs',
|
|
severity: 3,
|
|
text: '',
|
|
},
|
|
},
|
|
{text: ''},
|
|
{text: 'warning: 2 warnings emitted'},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Tool output', () => {
|
|
it('removes the relative path', () => {
|
|
expect(
|
|
utils.parseOutput(
|
|
'./example.cpp:1:1: Fatal: There were 1 errors compiling module, stopping',
|
|
'./example.cpp',
|
|
),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
column: 1,
|
|
line: 1,
|
|
text: 'Fatal: There were 1 errors compiling module, stopping',
|
|
severity: 3,
|
|
file: 'example.cpp',
|
|
},
|
|
text: '<source>:1:1: Fatal: There were 1 errors compiling module, stopping',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('removes fortran relative path', () => {
|
|
expect(
|
|
utils.parseOutput("./example.f90:5:22: error: No explicit type declared for 'y'", './example.f90'),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
column: 22,
|
|
line: 5,
|
|
text: "error: No explicit type declared for 'y'",
|
|
severity: 3,
|
|
file: 'example.f90',
|
|
},
|
|
text: "<source>:5:22: error: No explicit type declared for 'y'",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('removes the jailed path', () => {
|
|
expect(
|
|
utils.parseOutput(
|
|
'/home/ubuntu/example.cpp:1:1: Fatal: There were 1 errors compiling module, stopping',
|
|
'./example.cpp',
|
|
),
|
|
).toEqual([
|
|
{
|
|
tag: {
|
|
column: 1,
|
|
line: 1,
|
|
text: 'Fatal: There were 1 errors compiling module, stopping',
|
|
severity: 3,
|
|
file: 'example.cpp',
|
|
},
|
|
text: '<source>:1:1: Fatal: There were 1 errors compiling module, stopping',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Anonymizes all kind of IPs', () => {
|
|
it('Ignores localhost', () => {
|
|
expect(utils.anonymizeIp('localhost')).toEqual('localhost');
|
|
expect(utils.anonymizeIp('localhost:42')).toEqual('localhost:42');
|
|
});
|
|
it('Removes last octet from IPv4 addresses', () => {
|
|
expect(utils.anonymizeIp('127.0.0.0')).toEqual('127.0.0.0');
|
|
expect(utils.anonymizeIp('127.0.0.10')).toEqual('127.0.0.0');
|
|
expect(utils.anonymizeIp('127.0.0.255')).toEqual('127.0.0.0');
|
|
});
|
|
it('Removes last 3 hextets from IPv6 addresses', () => {
|
|
// Not necessarily valid addresses, we're interested in the format
|
|
expect(utils.anonymizeIp('ffff:aaaa:dead:beef')).toEqual('ffff:0:0:0');
|
|
expect(utils.anonymizeIp('bad:c0de::')).toEqual('bad:0:0:0');
|
|
expect(utils.anonymizeIp(':1d7e::c0fe')).toEqual(':0:0:0');
|
|
});
|
|
});
|
|
|
|
describe('Logger functionality', () => {
|
|
it('correctly logs streams split over lines', () => {
|
|
const logs: {level: string; msg: string}[] = [];
|
|
const fakeLog = {log: (level: string, msg: string) => logs.push({level, msg})} as any as winston.Logger;
|
|
const infoStream = makeLogStream('info', fakeLog);
|
|
infoStream.write('first\n');
|
|
infoStream.write('part');
|
|
infoStream.write('ial\n');
|
|
expect(logs).toEqual([
|
|
{
|
|
level: 'info',
|
|
msg: 'first',
|
|
},
|
|
{
|
|
level: 'info',
|
|
msg: 'partial',
|
|
},
|
|
]);
|
|
});
|
|
it('correctly logs streams to the right destination', () => {
|
|
const logs: {level: string; msg: string}[] = [];
|
|
const fakeLog = {log: (level: string, msg: string) => logs.push({level, msg})} as any as winston.Logger;
|
|
const infoStream = makeLogStream('warn', fakeLog);
|
|
infoStream.write('ooh\n');
|
|
expect(logs).toEqual([
|
|
{
|
|
level: 'warn',
|
|
msg: 'ooh',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Hash interface', () => {
|
|
it('correctly hashes strings', () => {
|
|
const version = 'Compiler Explorer Tests Version 0';
|
|
expect(utils.getHash('cream cheese', version)).toEqual(
|
|
'cfff2d1f7a213e314a67cce8399160ae884f794a3ee9d4a01cd37a8c22c67d94',
|
|
);
|
|
expect(utils.getHash('large eggs', version)).toEqual(
|
|
'9144dec50b8df5bc5cc24ba008823cafd6616faf2f268af84daf49ac1d24feb0',
|
|
);
|
|
expect(utils.getHash('sugar', version)).toEqual(
|
|
'afa3c89d0f6a61de6805314c9bd7c52d020425a3a3c7bbdfa7c0daec594e5ef1',
|
|
);
|
|
});
|
|
it('correctly hashes objects', () => {
|
|
expect(
|
|
utils.getHash({
|
|
toppings: [
|
|
{name: 'raspberries', optional: false},
|
|
{name: 'ground cinnamon', optional: true},
|
|
],
|
|
}),
|
|
).toEqual('e205d63abd5db363086621fdc62c4c23a51b733bac5855985a8b56642d570491');
|
|
});
|
|
});
|
|
|
|
describe('GoldenLayout utils', () => {
|
|
it('finds every editor & compiler', async () => {
|
|
const state = JSON.parse(await fs.readFile('test/example-states/default-state.json', 'utf-8'));
|
|
const contents = utils.glGetMainContents(state.content);
|
|
expect(contents).toEqual({
|
|
editors: [
|
|
{source: 'Editor 1', language: 'c++'},
|
|
{source: 'Editor 2', language: 'c++'},
|
|
{source: 'Editor 3', language: 'c++'},
|
|
{source: 'Editor 4', language: 'c++'},
|
|
],
|
|
compilers: [
|
|
{compiler: 'clang_trunk'},
|
|
{compiler: 'gsnapshot'},
|
|
{compiler: 'clang_trunk'},
|
|
{compiler: 'gsnapshot'},
|
|
{compiler: 'rv32-clang'},
|
|
],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('squashes horizontal whitespace', () => {
|
|
it('handles empty input', () => {
|
|
expect(utils.squashHorizontalWhitespace('')).toEqual('');
|
|
expect(utils.squashHorizontalWhitespace(' ')).toEqual('');
|
|
expect(utils.squashHorizontalWhitespace(' ')).toEqual('');
|
|
});
|
|
it('handles leading spaces', () => {
|
|
expect(utils.squashHorizontalWhitespace(' abc')).toEqual(' abc');
|
|
expect(utils.squashHorizontalWhitespace(' abc')).toEqual(' abc');
|
|
expect(utils.squashHorizontalWhitespace(' abc')).toEqual(' abc');
|
|
});
|
|
it('handles interline spaces', () => {
|
|
expect(utils.squashHorizontalWhitespace('abc abc')).toEqual('abc abc');
|
|
expect(utils.squashHorizontalWhitespace('abc abc')).toEqual('abc abc');
|
|
expect(utils.squashHorizontalWhitespace('abc abc')).toEqual('abc abc');
|
|
});
|
|
it('handles leading and interline spaces', () => {
|
|
expect(utils.squashHorizontalWhitespace(' abc abc')).toEqual(' abc abc');
|
|
expect(utils.squashHorizontalWhitespace(' abc abc')).toEqual(' abc abc');
|
|
expect(utils.squashHorizontalWhitespace(' abc abc')).toEqual(' abc abc');
|
|
expect(utils.squashHorizontalWhitespace(' abc abc')).toEqual(' abc abc');
|
|
});
|
|
});
|
|
|
|
describe('encodes in our version of base32', () => {
|
|
function doTest(original: string, expected: string) {
|
|
expect(utils.base32Encode(Buffer.from(original))).toEqual(expected);
|
|
}
|
|
|
|
// Done by hand to check that they are valid
|
|
|
|
it('works for empty strings', () => {
|
|
doTest('', '');
|
|
});
|
|
|
|
it('works for lengths multiple of 5 bits', () => {
|
|
doTest('aaaaa', '3Mn4ha7P');
|
|
});
|
|
|
|
it('works for lengths not multiple of 5 bits', () => {
|
|
// 3
|
|
doTest('a', '35');
|
|
|
|
// 1
|
|
doTest('aa', '3Mn1');
|
|
|
|
// 4
|
|
doTest('aaa', '3Mn48');
|
|
|
|
// 2
|
|
doTest('aaaa', '3Mn4ha3');
|
|
});
|
|
|
|
it('works for some random strings', () => {
|
|
// I also calculated this ones so lets put them
|
|
doTest('foo', '8rrx8');
|
|
|
|
doTest('foobar', '8rrx8b7Pc5');
|
|
});
|
|
});
|
|
|
|
const thisFilename = fileURLToPath(import.meta.url);
|
|
|
|
describe('fileExists', () => {
|
|
it('Returns true for files that exists', async () => {
|
|
await expect(utils.fileExists(thisFilename)).resolves.toBe(true);
|
|
});
|
|
it("Returns false for files that don't exist", async () => {
|
|
await expect(utils.fileExists('./ABC-FileThatDoesNotExist.extension')).resolves.toBe(false);
|
|
});
|
|
it('Returns false for directories that exist', async () => {
|
|
await expect(utils.fileExists(path.resolve(path.dirname(thisFilename)))).resolves.toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('safe semver', () => {
|
|
it('should understand most kinds of semvers', () => {
|
|
expect(utils.asSafeVer('0')).toEqual('0.0.0');
|
|
expect(utils.asSafeVer('1')).toEqual('1.0.0');
|
|
|
|
expect(utils.asSafeVer('1.0')).toEqual('1.0.0');
|
|
expect(utils.asSafeVer('1.1')).toEqual('1.1.0');
|
|
|
|
expect(utils.asSafeVer('1.1.0')).toEqual('1.1.0');
|
|
expect(utils.asSafeVer('1.1.1')).toEqual('1.1.1');
|
|
|
|
expect(utils.asSafeVer('trunk')).toEqual(utils.magic_semver.trunk);
|
|
expect(utils.asSafeVer('(trunk)')).toEqual(utils.magic_semver.trunk);
|
|
expect(utils.asSafeVer('(123.456.789 test)')).toEqual(utils.magic_semver.non_trunk);
|
|
|
|
expect(utils.asSafeVer('0..0')).toEqual(utils.magic_semver.non_trunk);
|
|
expect(utils.asSafeVer('0.0.')).toEqual(utils.magic_semver.non_trunk);
|
|
expect(utils.asSafeVer('0.')).toEqual(utils.magic_semver.non_trunk);
|
|
expect(utils.asSafeVer('.0.0')).toEqual(utils.magic_semver.non_trunk);
|
|
expect(utils.asSafeVer('.0..')).toEqual(utils.magic_semver.non_trunk);
|
|
expect(utils.asSafeVer('0..')).toEqual(utils.magic_semver.non_trunk);
|
|
|
|
expect(utils.asSafeVer('123 TEXT')).toEqual('123.0.0');
|
|
expect(utils.asSafeVer('123.456 TEXT')).toEqual('123.456.0');
|
|
expect(utils.asSafeVer('123.456.789 TEXT')).toEqual('123.456.789');
|
|
});
|
|
});
|
|
|
|
describe('tries to load text file', () => {
|
|
it('should load files that exist', async () => {
|
|
const selfText = await utils.tryReadTextFile(thisFilename);
|
|
expect(selfText).toContain('should load files that exist');
|
|
});
|
|
it('should be undefined for non-existent files', async () => {
|
|
const selfText = await utils.tryReadTextFile(thisFilename + '.doesntexist');
|
|
expect(selfText).to.be.undefined;
|
|
});
|
|
});
|
|
|
|
describe('output files', async () => {
|
|
const tmpDir = newTempDir();
|
|
it('should work in simple cases', async () => {
|
|
const filepath = path.join(tmpDir, 'file.txt');
|
|
await utils.outputTextFile(filepath, 'hello');
|
|
expect(await utils.tryReadTextFile(filepath)).toEqual('hello');
|
|
});
|
|
it('should create subddirectories ok', async () => {
|
|
const filepath = path.join(tmpDir, 'a', 'b', 'file.txt');
|
|
await utils.outputTextFile(filepath, 'hello');
|
|
expect(await utils.tryReadTextFile(filepath)).toEqual('hello');
|
|
});
|
|
it('should ensure files exist', async () => {
|
|
const filepath = path.join(tmpDir, 'moo', 'foo', 'file.txt');
|
|
await utils.ensureFileExists(filepath);
|
|
expect(await utils.tryReadTextFile(filepath)).toEqual('');
|
|
await utils.outputTextFile(filepath, 'hello');
|
|
expect(await utils.tryReadTextFile(filepath)).toEqual('hello');
|
|
await utils.ensureFileExists(filepath);
|
|
expect(await utils.tryReadTextFile(filepath)).toEqual('hello');
|
|
});
|
|
});
|