blob: 36735b2b3c8845c2f17a4c37ea245f755fc2e4f8 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import chai from 'chai'; // not esm
import {LitElement} from 'lit';
import { LogRow } from '../components/log_row';
import { FilterExpression, OrExpression, parseFilter } from '../src/filter';
import {ffxLogToLogRowData} from '../src/ffxLogToLogRow';
import {State} from '../src/state';
import {LogColumnFormatter} from '../src/fields';
const columnFormatter: LogColumnFormatter = (_fieldName, text) => text;
before(() => {
chai.should();
});
describe('filtering', () => {
let testLitElements: LitElement[] = [];
let testElements: HTMLElement[] = [];
let state: State;
before(async () => {
state = new State();
testLitElements = [
new LogRow(
ffxLogToLogRowData(
{
data: {
// eslint-disable-next-line @typescript-eslint/naming-convention
TargetLog: {
moniker: 'core/hello-world',
metadata: {
errors: [],
// eslint-disable-next-line @typescript-eslint/naming-convention
component_url: 'fuchsia-pkg://fuchsia.com/hello#meta/logs.cm',
timestamp: 123456789,
severity: 'Info',
tags: ['fofo', 'bar',],
pid: 123,
tid: 456,
file: 'foo/main.rs',
line: 25,
},
payload: {
root: {
message: {
value: 'hello vscode'
},
keys: {
'string': 'oh no'
},
printf: undefined
},
},
},
},
timestamp: 12345,
version: 1
}, false)!, state.currentFields, columnFormatter),
new LogRow( //2
ffxLogToLogRowData(
{
data: {
// eslint-disable-next-line @typescript-eslint/naming-convention
TargetLog: {
moniker: 'core/bye-world',
metadata: {
errors: [],
// eslint-disable-next-line @typescript-eslint/naming-convention
component_url: 'fuchsia-pkg://fuchsia.com/bye#meta/logging.cm',
timestamp: 123456789,
severity: 'error',
tags: ['foo'],
pid: 789,
tid: 987,
file: 'foo/main.rs',
line: 25,
},
payload: {
root: {
message: {
value: 'goodbye vscode'
},
keys: {
'num': 5,
'bool': false,
'string': 'hey hey',
},
printf: undefined
},
},
},
},
timestamp: 12345,
version: 1
}, false)!, state.currentFields, columnFormatter),
new LogRow(
ffxLogToLogRowData(
{
data: {
// eslint-disable-next-line @typescript-eslint/naming-convention
TargetLog: {
moniker: 'bootstrap/hello-world',
metadata: {
errors: [],
// eslint-disable-next-line @typescript-eslint/naming-convention
component_url: 'fuchsia-pkg://fuchsia.com/hello#meta/logs.cm',
timestamp: 123456789,
severity: 'warn',
tags: ['baz'],
pid: 123,
tid: 789,
file: 'foo/main.rs',
line: 25,
},
payload: {
root: {
message: {
value: 'hello goodbye vscode'
},
keys: {
'num': 10,
'bool': true,
'string': 'hey there',
},
printf: undefined
},
},
},
},
timestamp: 12345,
version: 1
}, false)!, state.currentFields, columnFormatter),
];
for (const index in testLitElements) {
document.body.appendChild(testLitElements[index]);
await testLitElements[index].updateComplete;
testElements.push(testLitElements[index].children[0] as HTMLElement);
}
});
after(() => {
for (const index in testLitElements) {
testLitElements[index].remove();
}
});
describe('FilterExpression', () => {
describe('accepts', () => {
it('returns true when no filters are set', () => {
const expr = new OrExpression([]);
expr.accepts(testElements[0]).should.be.true;
expr.accepts(testElements[1]).should.be.true;
});
it('ANDs the given filters', () => {
const result = parseFilter('moniker:hello-world severity:warn');
result.ok.should.be.true;
if (result.ok) {
result.value.accepts(testElements[0]).should.be.false;
result.value.accepts(testElements[1]).should.be.false;
result.value.accepts(testElements[2]).should.be.true;
}
});
it('ORs multiple categories', () => {
const result = parseFilter('(moniker:bootstrap | message="hello vscode")');
result.ok.should.be.true;
if (result.ok) {
result.value.accepts(testElements[0]).should.be.true;
result.value.accepts(testElements[1]).should.be.false;
result.value.accepts(testElements[2]).should.be.true;
}
});
it('applies not', () => {
const result = parseFilter('!moniker:bootstrap');
result.ok.should.be.true;
if (result.ok) {
result.value.accepts(testElements[0]).should.be.true;
result.value.accepts(testElements[1]).should.be.true;
result.value.accepts(testElements[2]).should.be.false;
}
});
});
});
describe('Filter', () => {
/**
* Utility that parses a filter string and returns the first filter parsed.
*
* @param expr a string with a filter
* @returns the `Filter` parsed.
*/
function singleFilter(expr: string): FilterExpression {
const result = parseFilter(expr);
if (result.ok) {
return result.value;
}
throw Error(`failed to parse ${expr}: ${result.error}`);
}
describe('accepts', () => {
describe('ORs the potential values of filters', () => {
it('supports ORing multiple values for single category', () => {
const filter = singleFilter('severity=warn|error');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
});
});
describe('severity', () => {
it('handles minimum severity', () => {
let filter = singleFilter('severity:error');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('severity>=warn');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('severity>warn');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
it('handles exact severity', () => {
let filter = singleFilter('severity=warn');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
it('handles maximum severity', () => {
let filter = singleFilter('severity<warn');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('severity<=warn');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
});
describe('moniker', () => {
it('handles contains', () => {
let filter = singleFilter('moniker:hello');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
it('handles equals', () => {
let filter = singleFilter('moniker=core/bye-world');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
it('handles regexes', () => {
let filter = singleFilter('moniker=/^core.+$/');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('moniker:/he.*lo/');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
});
describe('pid, tid', () => {
it('handle contains', () => {
let filter = singleFilter('pid:123');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('tid:456');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.false;
});
it('handle equals', () => {
let filter = singleFilter('pid=123');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('tid=456');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.false;
});
});
describe('tag', () => {
it('handles contains', () => {
let filter = singleFilter('tag:fo');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
it('handles equals', () => {
let filter = singleFilter('tag=foo');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
});
describe('package-name', () => {
it('handles contains', () => {
let filter = singleFilter('package-name:lo');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
it('handles equals', () => {
let filter = singleFilter('package-name=bye');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
});
describe('manifest', () => {
it('handles contains', () => {
let filter = singleFilter('manifest:log');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
});
it('handles equals', () => {
let filter = singleFilter('manifest=logs.cm');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
});
describe('message', () => {
it('handles contains', () => {
let filter = singleFilter('message:goodbye');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
});
it('handles equals', () => {
let filter = singleFilter('message="goodbye vscode"');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
});
describe('custom', () => {
it('supports string ops on string fields', () => {
let filter = singleFilter('string:hey');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('string="hey hey"');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
it('supports equals and contains with regexes', () => {
let filter = singleFilter('string:/^hey/');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('string=/o.*no/');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.false;
});
it('supports numeric ops on number fields', () => {
let filter = singleFilter('num>3');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('num>=10');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('num=5');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('num:10');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('num<10');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('num<=10');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
});
it('supports equals and contains (as equals) on boolean fields', () => {
let filter = singleFilter('bool=true');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('bool:false');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
});
});
describe('any', () => {
it('supports string ops on any field', () => {
let filter = singleFilter('any=hello');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('any:hey');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.true;
});
it('supports raw strings', () => {
let filter = singleFilter('hello');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
filter = singleFilter('not hello');
filter.accepts(testElements[0]).should.be.false;
filter.accepts(testElements[1]).should.be.true;
filter.accepts(testElements[2]).should.be.false;
filter = singleFilter('"hello vscode"');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.false;
});
it('supports raw regexes', () => {
let filter = singleFilter('/hel+o/');
filter.accepts(testElements[0]).should.be.true;
filter.accepts(testElements[1]).should.be.false;
filter.accepts(testElements[2]).should.be.true;
});
});
});
});
});