| // 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 { Category, Filter, FilterExpression, Operator, 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)) { |
| const filters = parseFilter(`${category}${opSym}${value}`); |
| filters.should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: category as Category, |
| subCategory: undefined, |
| operator: op as Operator, |
| values: [value], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| } |
| } |
| }); |
| |
| 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 FilterExpression([ |
| [ |
| new Filter({ |
| category: category as Category, |
| subCategory: undefined, |
| operator: op as Operator, |
| values: [value], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| } |
| } |
| }); |
| |
| 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 FilterExpression([ |
| [ |
| new Filter({ |
| category: 'custom', |
| subCategory: 'some_key', |
| operator: op as Operator, |
| values: [value] as (string[] | number[] | boolean[]), |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| } |
| }); |
| |
| 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 FilterExpression([ |
| [ |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: op as Operator, |
| values: [severity], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| } |
| } |
| // An invalid severity is treated as a custom key. |
| parseFilter('severity:foo').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'custom', |
| subCategory: 'severity', |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| 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 FilterExpression([ |
| [ |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['info'], |
| not: true, |
| }) |
| ] |
| ]) |
| }); |
| } |
| }); |
| |
| it('supports spaces and reserved chars inside quotes', () => { |
| const filters = parseFilter('message:"this is:some message"'); |
| filters.should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'message', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['this is:some message'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('support multiple possible values', () => { |
| parseFilter('tag=foo|"bar baz"|quux').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'tag', |
| subCategory: undefined, |
| operator: 'equal', |
| values: ['foo', 'bar baz', 'quux'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| |
| parseFilter('foo=3|4').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'custom', |
| subCategory: 'foo', |
| operator: 'equal', |
| values: [3, 4], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| |
| parseFilter('severity=info|debug').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: 'equal', |
| values: ['info', 'debug'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('is case insensitive for reserved keywords', () => { |
| parseFilter('SeveriTy:info').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['info'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('is case senstive for custom keys', () => { |
| parseFilter('SOME_key=true').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'custom', |
| subCategory: 'SOME_key', |
| operator: 'equal', |
| values: [true], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| }); |
| |
| it('accepts ORed items', () => { |
| parseFilter('(moniker:foo | !tag:bar Or severity=info)').should.deep.equal( |
| { |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'moniker', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }), |
| new Filter({ |
| category: 'tag', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['bar'], |
| not: true, |
| }), |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: 'equal', |
| values: ['info'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| 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 FilterExpression([ |
| [ |
| new Filter({ |
| category: 'moniker', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }), |
| new Filter({ |
| category: 'tag', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['bar', 'baz quux'], |
| not: true, |
| }), |
| ], |
| [ |
| new Filter({ |
| category: 'severity', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['info'], |
| not: false, |
| }), |
| ], |
| [ |
| new Filter({ |
| category: 'tag', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }), |
| ], |
| [ |
| new Filter({ |
| category: 'message', |
| subCategory: undefined, |
| operator: 'equal', |
| values: ['bar'], |
| not: false, |
| }), |
| ] |
| ]) |
| }); |
| }); |
| |
| describe('raw strings', () => { |
| it('can be used as "any" filters', () => { |
| parseFilter('foo').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('multiple raw strings form a conjunction', () => { |
| parseFilter('foo bar').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: false, |
| }), |
| ], |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['bar'], |
| not: false, |
| }) |
| ] |
| ]), |
| }); |
| }); |
| |
| it('multiple strings inside quotes is a single string', () => { |
| parseFilter('"foo bar"').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo bar'], |
| not: false, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('parses not before the string', () => { |
| parseFilter('NOT foo').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo'], |
| not: true, |
| }) |
| ] |
| ]) |
| }); |
| parseFilter('!"foo bar"').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['foo bar'], |
| not: true, |
| }) |
| ] |
| ]) |
| }); |
| }); |
| |
| it('can be mixed with other filters', () => { |
| parseFilter('moniker:core/bar cool').should.deep.equal({ |
| ok: true, |
| value: new FilterExpression([ |
| [ |
| new Filter({ |
| category: 'moniker', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['core/bar'], |
| not: false, |
| }), |
| ], |
| [ |
| new Filter({ |
| category: 'any', |
| subCategory: undefined, |
| operator: 'contains', |
| values: ['cool'], |
| not: false, |
| }) |
| ] |
| ]), |
| }); |
| }); |
| }); |
| }); |