blob: 94ac97a9141502e05c6a6259d0c891ff91ba0bd9 [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 { AndExpression, Category, Filter, NotExpression, Operator, OrExpression, parseFilter } from '../src/filter';
before(() => {
chai.should();
});
describe('Parser', function () {
describe('single item', () => {
it('supports common metadata fields', () => {
const toTest = {
'any': 'foo',
'manifest': 'foo.cm',
'moniker': 'core/foo',
'message': 'hello',
'package-name': 'my-package',
'tag': 'bar',
};
const ops = {
'equal': '=',
'contains': ':'
};
for (const [category, value] of Object.entries(toTest)) {
for (const [op, opSym] of Object.entries(ops)) {
let filters = parseFilter(`${category}${opSym}${value}`);
filters.should.deep.equal({
ok: true,
value: new Filter({
category: category as Category,
subCategory: undefined,
operator: op as Operator,
value,
}),
});
filters = parseFilter(`${category}${opSym}/${value.replace('/', '\\/')}/`);
filters.should.deep.equal({
ok: true,
value: new Filter({
category: category as Category,
subCategory: undefined,
operator: op as Operator,
value: new RegExp(value),
}),
});
}
}
});
it('support pid and tid', () => {
const ops = {
'equal': '=',
'contains': ':'
};
const toTest = {
'pid': 1234,
'tid': 5678,
};
for (const [category, value] of Object.entries(toTest)) {
for (const [op, opSym] of Object.entries(ops)) {
const filters = parseFilter(`${category}${opSym}${value}`);
filters.should.deep.equal({
ok: true,
value: new Filter({
category: category as Category,
subCategory: undefined,
operator: op as Operator,
value: value,
})
});
}
}
});
it('supports custom keys', () => {
const ops = {
'equal': ['=', true],
'contains': [':', 'foo'],
'greaterEq': ['>=', 3],
'lessEq': ['<=', 5],
'greater': ['>', 3],
'less': ['<', 5],
};
for (const [op, [opSym, value]] of Object.entries(ops)) {
const filters = parseFilter(`some_key${opSym}${value}`);
filters.should.deep.equal({
ok: true,
value: new Filter({
category: 'custom',
subCategory: 'some_key',
operator: op as Operator,
value,
})
});
}
});
it('supports severity levels and only valid severities', () => {
const severities = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
const ops = {
'equal': '=',
'contains': ':',
'greaterEq': '>=',
'lessEq': '<=',
'greater': '>',
'less': '<',
};
for (const severity of severities) {
for (const [op, opSym] of Object.entries(ops)) {
const filters = parseFilter(`severity${opSym}${severity}`);
filters.should.deep.equal({
ok: true,
value: new Filter({
category: 'severity',
subCategory: undefined,
operator: op as Operator,
value: severity,
})
});
}
}
// An invalid severity is treated as a custom key.
parseFilter('severity:foo').should.deep.equal({
ok: true,
value: new Filter({
category: 'custom',
subCategory: 'severity',
operator: 'contains',
value: 'foo',
})
});
});
it('supports warning as an alias of warn', () => {
parseFilter('severity:warning').should.deep.equal({
ok: true,
value: new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'warn',
})
});
});
it('supports negations', () => {
const ops = ['!', 'not '];
for (const op of ops) {
const filters = parseFilter(`${op}severity:info`);
filters.should.deep.equal({
ok: true,
value: new NotExpression(new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info',
}))
});
}
});
it('supports spaces and reserved chars inside quotes', () => {
const filters = parseFilter('message:"this is:some message"');
filters.should.deep.equal({
ok: true,
value:
new Filter({
category: 'message',
subCategory: undefined,
operator: 'contains',
value: 'this is:some message',
})
});
});
it('supports multiple ORed values', () => {
parseFilter('tag=foo|"bar baz"|quux').should.deep.equal({
ok: true,
value: new OrExpression([
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'foo',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'bar baz',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'quux',
}),
]),
});
parseFilter('foo=3|4').should.deep.equal({
ok: true,
value: new OrExpression([
new Filter({
category: 'custom',
subCategory: 'foo',
operator: 'equal',
value: 3,
}),
new Filter({
category: 'custom',
subCategory: 'foo',
operator: 'equal',
value: 4,
}),
])
});
parseFilter('severity=info|debug').should.deep.equal({
ok: true,
value: new OrExpression([
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'equal',
value: 'info',
}),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'equal',
value: 'debug',
}),
]),
});
});
it('supports multiple ANDed values', () => {
parseFilter('tag=foo&"bar baz"&quux').should.deep.equal({
ok: true,
value: new AndExpression([
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'foo',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'bar baz',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'equal',
value: 'quux',
}),
]),
});
parseFilter('foo=3&4').should.deep.equal({
ok: true,
value: new AndExpression([
new Filter({
category: 'custom',
subCategory: 'foo',
operator: 'equal',
value: 3,
}),
new Filter({
category: 'custom',
subCategory: 'foo',
operator: 'equal',
value: 4,
}),
])
});
parseFilter('severity=info&debug').should.deep.equal({
ok: true,
value: new AndExpression([
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'equal',
value: 'info',
}),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'equal',
value: 'debug',
}),
]),
});
});
it('is case insensitive for reserved keywords', () => {
parseFilter('SeveriTy:info').should.deep.equal({
ok: true,
value: new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info',
})
});
});
it('is case senstive for custom keys', () => {
parseFilter('SOME_key=true').should.deep.equal({
ok: true,
value: new Filter({
category: 'custom',
subCategory: 'SOME_key',
operator: 'equal',
value: true,
})
});
});
});
it('accepts ORed items', () => {
parseFilter('moniker:foo OR !severity<info').should.deep.equal(
{
ok: true,
value:
new OrExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'foo',
}),
new NotExpression(new Filter({
category: 'severity',
subCategory: undefined,
operator: 'less',
value: 'info',
})),
])
});
parseFilter('(moniker:foo | !tag:bar Or severity=info)').should.deep.equal(
{
ok: true,
value:
new OrExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'foo',
}),
new NotExpression(new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'bar',
})),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'equal',
value: 'info',
})
])
});
});
it('accepts multiple ANDed expressions', () => {
parseFilter('(moniker:foo or !tag:bar|"baz quux") severity:info & tag:foo and message=bar')
.should.deep.equal({
ok: true,
value: new AndExpression(
[
new OrExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'foo',
}),
new NotExpression(new OrExpression([
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'bar',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'baz quux',
}),
])),
]),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info',
}),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'foo',
}),
new Filter({
category: 'message',
subCategory: undefined,
operator: 'equal',
value: 'bar',
}),
]
)
});
});
describe('raw strings parser', () => {
it('can be used as "any" filters', () => {
parseFilter('foo').should.deep.equal({
ok: true,
value: new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo',
})
});
});
it('handles escaped chars', () => {
parseFilter('foo"').ok.should.be.false;
parseFilter('foo\\"').should.deep.equal({
ok: true,
value: new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo"',
})
});
parseFilter('"foo""').ok.should.be.false;
parseFilter('"foo\\""').should.deep.equal({
ok: true,
value: new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo"',
})
});
});
it('accepts multiple raw strings form a conjunction', () => {
parseFilter('foo bar').should.deep.equal({
ok: true,
value: new AndExpression([
new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo',
}),
new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'bar',
})
])
});
});
it('accepts multiple strings inside quotes is a single string', () => {
parseFilter('"foo bar"').should.deep.equal({
ok: true,
value: new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo bar',
})
});
});
it('parses not before the string', () => {
parseFilter('NOT foo').should.deep.equal({
ok: true,
value: new NotExpression(
new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo',
})
)
});
parseFilter('!"foo bar"').should.deep.equal({
ok: true,
value: new NotExpression(
new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'foo bar',
})
)
});
});
it('can be mixed with other filters', () => {
parseFilter('moniker:core/bar cool').should.deep.equal({
ok: true,
value: new AndExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'core/bar',
}),
new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: 'cool',
})
]),
});
});
});
describe('raw regexes parser', () => {
it('accepts raw regexes as input', () => {
parseFilter('/foo\\/bar/').should.deep.equal({
ok: true,
value: new Filter({
category: 'any',
subCategory: undefined,
operator: 'contains',
value: new RegExp('foo/bar'),
})
});
});
});
describe('parse logical expressions', () => {
it('supports nesting arbitrary expressions', () => {
parseFilter('(moniker:foo or moniker:bar) severity:info (tag:foo or !(foo=bar and !k<2))')
.should.deep.equal({
ok: true,
value: new AndExpression([
new OrExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'foo'
}),
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'bar'
})
]),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info'
}),
new OrExpression([
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'foo'
}),
new NotExpression(new AndExpression([
new Filter({
category: 'custom',
subCategory: 'foo',
operator: 'equal',
value: 'bar'
}),
new NotExpression(new Filter({
category: 'custom',
subCategory: 'k',
operator: 'less',
value: 2
}))
])),
])
])
});
parseFilter('!(((moniker:foo or severity:info) & tag:bar) or package-name:baz) message:crash')
.should.deep.equal({
ok: true,
value: new AndExpression([
new NotExpression(
new OrExpression([
new AndExpression([
new OrExpression([
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'foo'
}),
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info'
}),
]),
new Filter({
category: 'tag',
subCategory: undefined,
operator: 'contains',
value: 'bar'
})
]),
new Filter({
category: 'package-name',
subCategory: undefined,
operator: 'contains',
value: 'baz'
}),
]),
),
new Filter({
category: 'message',
subCategory: undefined,
operator: 'contains',
value: 'crash'
})
]),
});
});
it('makes conjunction take precedence over disjunction', () => {
parseFilter('severity:info and moniker:bar or moniker:baz').should.deep.equal({
ok: true,
value: new OrExpression([
new AndExpression([
new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info'
}),
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'bar'
})
]),
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'baz'
})
]),
});
});
it('makes not take precedence over and', () => {
parseFilter('not severity:info and moniker:bar or not moniker:baz').should.deep.equal({
ok: true,
value: new OrExpression([
new AndExpression([
new NotExpression(new Filter({
category: 'severity',
subCategory: undefined,
operator: 'contains',
value: 'info'
})),
new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'bar'
})
]),
new NotExpression(new Filter({
category: 'moniker',
subCategory: undefined,
operator: 'contains',
value: 'baz'
}))
]),
});
});
});
});