// Copyright (C) 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { c, cmpFromExpr, cmpFromSort, ConcreteEventSet, Direction, EmptyEventSet, EmptyKeySet, eq, Event, EventSet, isConcreteEventSet, isEmptyEventSet, KeySet, Num, Str, UntypedEvent, v, } from './event_set'; describe('EventSet', () => { test('Event', () => { { const keyset: EmptyKeySet = {}; const event: Event = { id: 'foo', }; void event; } { const keyset = { bar: Num, }; const event: Event = { id: 'foo', bar: 42, }; void event; } }); describe('EmptyEventSet', () => { test('isEmpty', async () => { const events = EmptyEventSet.get(); expect(await events.isEmpty()).toEqual(true); expect(await events.count()).toEqual(0); }); test('isEmptyEventSet', () => { const events: EventSet = EmptyEventSet.get(); expect(isEmptyEventSet(events)).toEqual(true); }); test('materialise', async () => { const events: EventSet = EmptyEventSet.get(); const materialised = await events.materialise({}); expect(await materialised.isEmpty()).toEqual(true); expect(await materialised.count()).toEqual(0); expect(materialised.events).toEqual([]); expect(isConcreteEventSet(materialised)).toEqual(true); }); test('union', async () => { const a: EventSet = EmptyEventSet.get(); const b: EventSet = EmptyEventSet.get(); const aUnionB = a.union(b); expect(await aUnionB.isEmpty()).toEqual(true); expect(await aUnionB.count()).toEqual(0); }); test('intersect', async () => { const a: EventSet = EmptyEventSet.get(); const b: EventSet = EmptyEventSet.get(); const aIntersectB = a.intersect(b); expect(await aIntersectB.isEmpty()).toEqual(true); expect(await aIntersectB.count()).toEqual(0); }); test('filter', async () => { const events: EventSet = EmptyEventSet.get(); const filtered = await events.filter(c(true)); expect(filtered).toBe(events); expect(await filtered.isEmpty()).toEqual(true); expect(await filtered.count()).toEqual(0); }); test('sort', async () => { const events: EventSet = EmptyEventSet.get(); const sorted = await events.sort({ direction: Direction.ASC, expression: c(0), }); expect(sorted).toBe(events); expect(await sorted.isEmpty()).toEqual(true); expect(await sorted.count()).toEqual(0); }); }); describe('ConcreteEventSet', () => { test('isEmpty', async () => { const event: Event = { id: 'foo', }; const empty = new ConcreteEventSet({}, []); const events = new ConcreteEventSet({}, [event]); expect(await empty.isEmpty()).toEqual(true); expect(await empty.count()).toEqual(0); expect(await events.isEmpty()).toEqual(false); expect(await events.count()).toEqual(1); }); test('isConcreteEventSet', () => { expect( isConcreteEventSet(new ConcreteEventSet({}, [])), ).toEqual(true); expect(isConcreteEventSet(EmptyEventSet.get())).toEqual(false); }); test('materialise', async () => { const keys = { num: Num, char: Str, }; const a: Event = { id: 'a', num: 97, char: 'a', }; const b: Event = { id: 'b', num: 98, char: 'b', }; const d: Event = { id: 'd', num: 100, char: 'd', }; const events = new ConcreteEventSet(keys, [a, b, d]); expect((await events.materialise(keys)).events).toEqual([a, b, d]); expect((await events.materialise(keys, 1)).events).toEqual([b, d]); expect((await events.materialise(keys, 1, 1)).events).toEqual([b]); expect((await events.materialise(keys, 99)).events).toEqual([]); expect((await events.materialise(keys, 99, 0)).events).toEqual([]); expect((await events.materialise({num: Num})).events).toEqual([ {id: 'a', num: 97}, {id: 'b', num: 98}, {id: 'd', num: 100}, ]); expect((await events.materialise({char: Str}, 1, 1)).events).toEqual([ {id: 'b', char: 'b'}, ]); }); test('union', async () => { const a: Event = { id: 'a', }; const b: Event = { id: 'b', }; const d: Event = { id: 'd', }; const empty = EmptyEventSet.get(); const justA = new ConcreteEventSet({}, [a]); const justB = new ConcreteEventSet({}, [b]); const justD = new ConcreteEventSet({}, [d]); const aAndB = justA.union(justB); const aAndA = justA.union(justA); const aAndD = justA.union(justD); const aAndBAndEmpty = aAndB.union(empty); const aAndDAndAAndB = aAndD.union(aAndB); expect((await aAndB.materialise({})).events).toEqual([a, b]); expect((await aAndA.materialise({})).events).toEqual([a]); expect((await aAndD.materialise({})).events).toEqual([a, d]); expect((await aAndBAndEmpty.materialise({})).events).toEqual([a, b]); expect((await aAndDAndAAndB.materialise({})).events).toEqual([a, d, b]); expect(await aAndB.isEmpty()).toEqual(false); expect(await aAndA.isEmpty()).toEqual(false); expect(await aAndD.isEmpty()).toEqual(false); expect(await aAndBAndEmpty.isEmpty()).toEqual(false); expect(await aAndDAndAAndB.isEmpty()).toEqual(false); expect(await aAndB.count()).toEqual(2); expect(await aAndA.count()).toEqual(1); expect(await aAndD.count()).toEqual(2); expect(await aAndBAndEmpty.count()).toEqual(2); expect(await aAndDAndAAndB.count()).toEqual(3); }); test('intersection', async () => { const a: Event = { id: 'a', }; const b: Event = { id: 'b', }; const d: Event = { id: 'd', }; const empty = EmptyEventSet.get(); const justA = new ConcreteEventSet({}, [a]); const justB = new ConcreteEventSet({}, [b]); const justD = new ConcreteEventSet({}, [d]); const aAndB = justA.intersect(justB); const aAndA = justA.intersect(justA); const aAndD = justA.intersect(justD); const aBAndEmpty = justA.union(justB).intersect(empty); const aDAndAB = justA.union(justB).intersect(justA.union(justD)); expect((await aAndB.materialise({})).events).toEqual([]); expect((await aAndA.materialise({})).events).toEqual([a]); expect((await aAndD.materialise({})).events).toEqual([]); expect((await aBAndEmpty.materialise({})).events).toEqual([]); expect((await aDAndAB.materialise({})).events).toEqual([a]); expect(await aAndB.isEmpty()).toEqual(true); expect(await aAndA.isEmpty()).toEqual(false); expect(await aAndD.isEmpty()).toEqual(true); expect(await aBAndEmpty.isEmpty()).toEqual(true); expect(await aDAndAB.isEmpty()).toEqual(false); expect(await aAndB.count()).toEqual(0); expect(await aAndA.count()).toEqual(1); expect(await aAndD.count()).toEqual(0); expect(await aBAndEmpty.count()).toEqual(0); expect(await aDAndAB.count()).toEqual(1); }); test('filter', async () => { const keys = { num: Num, char: Str, }; const a: Event = { id: 'a', num: 97, char: 'a', }; const b: Event = { id: 'b', num: 98, char: 'b', }; const d: Event = { id: 'd', num: 100, char: 'd', }; const events = new ConcreteEventSet(keys, [a, b, d]); const justA = events.filter(eq(v('id'), c('a'))); const justD = events.filter(eq(v('num'), c(100))); expect((await justA.materialise(keys)).events).toEqual([a]); expect((await justD.materialise(keys)).events).toEqual([d]); }); test('sort', async () => { const keys = { num: Num, char: Str, }; const a: Event = { id: 'a', num: 97, char: 'a', }; const b: Event = { id: 'b', num: 98, char: 'b', }; const d: Event = { id: 'd', num: 100, char: 'd', }; const events = new ConcreteEventSet(keys, [a, b, d]); const byNum = events.sort({ expression: v('num'), direction: Direction.ASC, }); const byStr = events.sort({ expression: v('char'), direction: Direction.ASC, }); expect((await byNum.materialise(keys)).events).toEqual([a, b, d]); expect((await byStr.materialise(keys)).events).toEqual([a, b, d]); }); test('sort desc', async () => { const keys = { num: Num, char: Str, }; const a: Event = { id: 'a', num: 97, char: 'a', }; const b: Event = { id: 'b', num: 98, char: 'b', }; const d: Event = { id: 'd', num: 100, char: 'd', }; const events = new ConcreteEventSet(keys, [a, b, d]); const byNum = events.sort({ expression: v('num'), direction: Direction.DESC, }); const byStr = events.sort({ expression: v('char'), direction: Direction.DESC, }); expect((await byNum.materialise(keys)).events).toEqual([d, b, a]); expect((await byStr.materialise(keys)).events).toEqual([d, b, a]); }); }); }); describe('cmpFromExpr', () => { test('simple', () => { const a: UntypedEvent = { id: 'a', x: 0, }; const b: UntypedEvent = { id: 'b', x: 42, }; const c: UntypedEvent = { id: 'c', x: 0, }; const cmp = cmpFromExpr(v('x')); expect(cmp(a, b)).toEqual(-1); expect(cmp(a, a)).toEqual(0); expect(cmp(b, a)).toEqual(1); expect(cmp(a, c)).toEqual(0); }); test('kinds', () => { const nullEvent: UntypedEvent = { id: 'nullEvent', x: null, }; const sevenEvent: UntypedEvent = { id: 'sevenEvent', x: 7, }; const oneEvent: UntypedEvent = { id: 'oneEvent', x: 1, }; const zeroEvent: UntypedEvent = { id: 'zeroEvent', x: 0, }; const trueEvent: UntypedEvent = { id: 'trueEvent', x: true, }; const falseEvent: UntypedEvent = { id: 'falseEvent', x: false, }; const aardvarkEvent: UntypedEvent = { id: 'aardvarkEvent', x: 'aardvark', }; const zigguratEvent: UntypedEvent = { id: 'zigguratEvent', x: 'ziggurat', }; const bigZeroEvent: UntypedEvent = { id: 'bigZeroEvent', x: 0n, }; const bigOneEvent: UntypedEvent = { id: 'bigOneEvent', x: 1n, }; const bigTwoEvent: UntypedEvent = { id: 'bigTwoEvent', x: 2n, }; const cmp = cmpFromExpr(v('x')); // Everything is equal to itself: expect(cmp(nullEvent, nullEvent)).toEqual(0); expect(cmp(sevenEvent, sevenEvent)).toEqual(0); expect(cmp(oneEvent, oneEvent)).toEqual(0); expect(cmp(zeroEvent, zeroEvent)).toEqual(0); expect(cmp(falseEvent, falseEvent)).toEqual(0); expect(cmp(trueEvent, trueEvent)).toEqual(0); expect(cmp(aardvarkEvent, aardvarkEvent)).toEqual(0); expect(cmp(zigguratEvent, zigguratEvent)).toEqual(0); expect(cmp(bigZeroEvent, bigZeroEvent)).toEqual(0); expect(cmp(bigOneEvent, bigOneEvent)).toEqual(0); expect(cmp(bigTwoEvent, bigTwoEvent)).toEqual(0); // BigInt(x) == x expect(cmp(bigZeroEvent, zeroEvent)).toEqual(0); expect(cmp(bigOneEvent, oneEvent)).toEqual(0); // one = true, zero = false: expect(cmp(oneEvent, trueEvent)).toEqual(0); expect(cmp(zeroEvent, falseEvent)).toEqual(0); expect(cmp(bigOneEvent, trueEvent)).toEqual(0); expect(cmp(bigZeroEvent, falseEvent)).toEqual(0); // 0 < 1 < 7 expect(cmp(zeroEvent, oneEvent)).toEqual(-1); expect(cmp(sevenEvent, oneEvent)).toEqual(1); // 0n < 1n < 2n expect(cmp(bigZeroEvent, bigOneEvent)).toEqual(-1); expect(cmp(bigTwoEvent, bigOneEvent)).toEqual(1); // 0 < 1n < 7 expect(cmp(zeroEvent, bigOneEvent)).toEqual(-1); expect(cmp(sevenEvent, bigOneEvent)).toEqual(1); // aardvark < ziggurat expect(cmp(aardvarkEvent, zigguratEvent)).toEqual(-1); // null < {bools, numbers, BigInt} < strings expect(cmp(nullEvent, falseEvent)).toEqual(-1); expect(cmp(aardvarkEvent, sevenEvent)).toEqual(1); expect(cmp(nullEvent, bigZeroEvent)).toEqual(-1); expect(cmp(bigZeroEvent, sevenEvent)).toEqual(-1); expect(cmp(nullEvent, falseEvent)).toEqual(-1); expect(cmp(falseEvent, sevenEvent)).toEqual(-1); }); }); describe('cmpFromSort', () => { test('simple asc', () => { const a: UntypedEvent = { id: 'a', x: 0, }; const b: UntypedEvent = { id: 'b', x: 42, }; const c: UntypedEvent = { id: 'c', x: 0, }; const cmp = cmpFromSort({ expression: v('x'), direction: Direction.ASC, }); expect(cmp(a, b)).toEqual(-1); expect(cmp(a, a)).toEqual(0); expect(cmp(b, a)).toEqual(1); expect(cmp(a, c)).toEqual(0); }); test('kinds asc', () => { const nullEvent: UntypedEvent = { id: 'nullEvent', x: null, }; const sevenEvent: UntypedEvent = { id: 'sevenEvent', x: 7, }; const oneEvent: UntypedEvent = { id: 'oneEvent', x: 1, }; const zeroEvent: UntypedEvent = { id: 'zeroEvent', x: 0, }; const trueEvent: UntypedEvent = { id: 'trueEvent', x: true, }; const falseEvent: UntypedEvent = { id: 'falseEvent', x: false, }; const aardvarkEvent: UntypedEvent = { id: 'aardvarkEvent', x: 'aardvark', }; const zigguratEvent: UntypedEvent = { id: 'zigguratEvent', x: 'ziggurat', }; const bigZeroEvent: UntypedEvent = { id: 'bigZeroEvent', x: 0n, }; const bigOneEvent: UntypedEvent = { id: 'bigOneEvent', x: 1n, }; const bigTwoEvent: UntypedEvent = { id: 'bigTwoEvent', x: 2n, }; const cmp = cmpFromSort({ expression: v('x'), direction: Direction.ASC, }); // Everything is equal to itself: expect(cmp(nullEvent, nullEvent)).toEqual(0); expect(cmp(sevenEvent, sevenEvent)).toEqual(0); expect(cmp(oneEvent, oneEvent)).toEqual(0); expect(cmp(zeroEvent, zeroEvent)).toEqual(0); expect(cmp(falseEvent, falseEvent)).toEqual(0); expect(cmp(trueEvent, trueEvent)).toEqual(0); expect(cmp(aardvarkEvent, aardvarkEvent)).toEqual(0); expect(cmp(zigguratEvent, zigguratEvent)).toEqual(0); expect(cmp(bigZeroEvent, bigZeroEvent)).toEqual(0); expect(cmp(bigOneEvent, bigOneEvent)).toEqual(0); expect(cmp(bigTwoEvent, bigTwoEvent)).toEqual(0); // BigInt(x) == x expect(cmp(bigZeroEvent, zeroEvent)).toEqual(0); expect(cmp(bigOneEvent, oneEvent)).toEqual(0); // one = true, zero = false: expect(cmp(oneEvent, trueEvent)).toEqual(0); expect(cmp(zeroEvent, falseEvent)).toEqual(0); expect(cmp(bigOneEvent, trueEvent)).toEqual(0); expect(cmp(bigZeroEvent, falseEvent)).toEqual(0); // 0 < 1 < 7 expect(cmp(zeroEvent, oneEvent)).toEqual(-1); expect(cmp(sevenEvent, oneEvent)).toEqual(1); // 0n < 1n < 2n expect(cmp(bigZeroEvent, bigOneEvent)).toEqual(-1); expect(cmp(bigTwoEvent, bigOneEvent)).toEqual(1); // 0 < 1n < 7 expect(cmp(zeroEvent, bigOneEvent)).toEqual(-1); expect(cmp(sevenEvent, bigOneEvent)).toEqual(1); // aardvark < ziggurat expect(cmp(aardvarkEvent, zigguratEvent)).toEqual(-1); // null < {bools, numbers, BigInt} < strings expect(cmp(nullEvent, falseEvent)).toEqual(-1); expect(cmp(aardvarkEvent, sevenEvent)).toEqual(1); expect(cmp(nullEvent, bigZeroEvent)).toEqual(-1); expect(cmp(bigZeroEvent, sevenEvent)).toEqual(-1); expect(cmp(nullEvent, falseEvent)).toEqual(-1); expect(cmp(falseEvent, sevenEvent)).toEqual(-1); }); test('simple desc', () => { const a: UntypedEvent = { id: 'a', x: 0, }; const b: UntypedEvent = { id: 'b', x: 42, }; const c: UntypedEvent = { id: 'c', x: 0, }; const cmp = cmpFromSort({ expression: v('x'), direction: Direction.DESC, }); expect(cmp(a, b)).toEqual(1); expect(cmp(a, a)).toEqual(0); expect(cmp(b, a)).toEqual(-1); expect(cmp(a, c)).toEqual(0); }); test('kinds desc', () => { const nullEvent: UntypedEvent = { id: 'nullEvent', x: null, }; const sevenEvent: UntypedEvent = { id: 'sevenEvent', x: 7, }; const oneEvent: UntypedEvent = { id: 'oneEvent', x: 1, }; const zeroEvent: UntypedEvent = { id: 'zeroEvent', x: 0, }; const trueEvent: UntypedEvent = { id: 'trueEvent', x: true, }; const falseEvent: UntypedEvent = { id: 'falseEvent', x: false, }; const aardvarkEvent: UntypedEvent = { id: 'aardvarkEvent', x: 'aardvark', }; const zigguratEvent: UntypedEvent = { id: 'zigguratEvent', x: 'ziggurat', }; const bigZeroEvent: UntypedEvent = { id: 'bigZeroEvent', x: 0n, }; const bigOneEvent: UntypedEvent = { id: 'bigOneEvent', x: 1n, }; const bigTwoEvent: UntypedEvent = { id: 'bigTwoEvent', x: 2n, }; const cmp = cmpFromSort({ expression: v('x'), direction: Direction.DESC, }); // Everything is equal to itself: expect(cmp(nullEvent, nullEvent)).toEqual(0); expect(cmp(sevenEvent, sevenEvent)).toEqual(0); expect(cmp(oneEvent, oneEvent)).toEqual(0); expect(cmp(zeroEvent, zeroEvent)).toEqual(0); expect(cmp(falseEvent, falseEvent)).toEqual(0); expect(cmp(trueEvent, trueEvent)).toEqual(0); expect(cmp(aardvarkEvent, aardvarkEvent)).toEqual(0); expect(cmp(zigguratEvent, zigguratEvent)).toEqual(0); expect(cmp(bigZeroEvent, bigZeroEvent)).toEqual(0); expect(cmp(bigOneEvent, bigOneEvent)).toEqual(0); expect(cmp(bigTwoEvent, bigTwoEvent)).toEqual(0); // BigInt(x) == x expect(cmp(bigZeroEvent, zeroEvent)).toEqual(0); expect(cmp(bigOneEvent, oneEvent)).toEqual(0); // one = true, zero = false: expect(cmp(oneEvent, trueEvent)).toEqual(0); expect(cmp(zeroEvent, falseEvent)).toEqual(0); expect(cmp(bigOneEvent, trueEvent)).toEqual(0); expect(cmp(bigZeroEvent, falseEvent)).toEqual(0); // 0 < 1 < 7 expect(cmp(zeroEvent, oneEvent)).toEqual(1); expect(cmp(sevenEvent, oneEvent)).toEqual(-1); // 0n < 1n < 2n expect(cmp(bigZeroEvent, bigOneEvent)).toEqual(1); expect(cmp(bigTwoEvent, bigOneEvent)).toEqual(-1); // 0 < 1n < 7 expect(cmp(zeroEvent, bigOneEvent)).toEqual(1); expect(cmp(sevenEvent, bigOneEvent)).toEqual(-1); // aardvark < ziggurat expect(cmp(aardvarkEvent, zigguratEvent)).toEqual(1); // null < {bools, numbers, BigInt} < strings expect(cmp(nullEvent, falseEvent)).toEqual(1); expect(cmp(aardvarkEvent, sevenEvent)).toEqual(-1); expect(cmp(nullEvent, bigZeroEvent)).toEqual(1); expect(cmp(bigZeroEvent, sevenEvent)).toEqual(1); expect(cmp(nullEvent, falseEvent)).toEqual(1); expect(cmp(falseEvent, sevenEvent)).toEqual(1); }); });