Skip to content

Commit

Permalink
test: add aggregation function tests
Browse files Browse the repository at this point in the history
  • Loading branch information
maartyman committed Jan 15, 2025
1 parent b837953 commit a9884ee
Show file tree
Hide file tree
Showing 27 changed files with 1,484 additions and 405 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type * as RDF from '@rdfjs/types';
import { termToString } from 'rdf-string';

interface IAverageState {
index: Map<string, number>;
sum: Eval.NumericLiteral;
count: number;
}
Expand All @@ -30,29 +31,44 @@ export class AverageAggregator extends AggregateEvaluator implements IBindingsAg
return Eval.typedLiteral('0', Eval.TypeURL.XSD_INTEGER);
}

public putTerm(term: RDF.Term): void {
protected putTerm(term: RDF.Term): void {
const hash = termToString(term);
const value = this.termToNumericOrError(term);
if (this.state === undefined) {
const sum = this.termToNumericOrError(term);
this.state = { sum, count: 1 };
} else {
const internalTerm = this.termToNumericOrError(term);
this.state.sum = <Eval.NumericLiteral> this.additionFunction
.applyOnTerms([ this.state.sum, internalTerm ], this.evaluator);
this.state.count++;
this.state = { index: new Map<string, number>([[ hash, 1 ]]), sum: value, count: 1 };
return;
}
this.state.index.set(hash, (this.state.index.get(hash) ?? 0) + 1);
this.state.sum = <Eval.NumericLiteral> this.additionFunction
.applyOnTerms([ this.state.sum, value ], this.evaluator);
this.state.count++;
}

protected removeTerm(term: RDF.Term): void {
const hash = termToString(term);
const value = this.termToNumericOrError(term);
if (this.state === undefined) {
throw new Error(`Cannot remove term ${termToString(term)} from empty average aggregator`);
}
const internalTerm = this.termToNumericOrError(term);
const count = this.state.index.get(hash);
if (count === undefined) {
throw new Error(`Cannot remove term ${termToString(term)} that was not added to average aggregator`);
}
if (count === 1) {
this.state.index.delete(hash);
if (this.state.count === 1) {
this.state = undefined;
return;
}
} else {
this.state.index.set(hash, count - 1);
}
this.state.sum = <Eval.NumericLiteral> this.subtractionFunction
.applyOnTerms([ this.state.sum, internalTerm ], this.evaluator);
.applyOnTerms([ this.state.sum, value ], this.evaluator);
this.state.count--;
}

public termResult(): RDF.Term | undefined {
protected termResult(): RDF.Term | undefined {
if (this.state === undefined) {
return this.emptyValue();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ActorFunctionFactoryTermAddition } from '@comunica/actor-function-factory-term-addition';
import { ActorFunctionFactoryTermDivision } from '@comunica/actor-function-factory-term-division';
import { ActorFunctionFactoryTermSubtraction } from '@comunica/actor-function-factory-term-subtraction';
import type { MediatorExpressionEvaluatorFactory } from '@comunica/bus-expression-evaluator-factory';
import type { MediatorFunctionFactory } from '@comunica/bus-function-factory';
import { createFuncMediator } from '@comunica/bus-function-factory/test/util';
import { Bus } from '@comunica/core';
import type { IActionContext } from '@comunica/types';
import {
createFuncMediator,
getMockEEActionContext,
getMockMediatorExpressionEvaluatorFactory,
makeAggregate,
} from '@comunica/utils-expression-evaluator/test/util/helpers';
} from '@incremunica/dev-tools';
import { ActorBindingsAggregatorFactoryAverage } from '../lib';
import '@comunica/utils-jest';

Expand All @@ -26,6 +27,7 @@ describe('ActorBindingsAggregatorFactoryAverage', () => {
mediatorExpressionEvaluatorFactory = getMockMediatorExpressionEvaluatorFactory();
mediatorFunctionFactory = createFuncMediator([
args => new ActorFunctionFactoryTermAddition(args),
args => new ActorFunctionFactoryTermSubtraction(args),
args => new ActorFunctionFactoryTermDivision(args),
], {});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { ActorFunctionFactoryTermAddition } from '@comunica/actor-function-factory-term-addition';
import { ActorFunctionFactoryTermDivision } from '@comunica/actor-function-factory-term-division';
import { ActorFunctionFactoryTermSubtraction } from '@comunica/actor-function-factory-term-subtraction';
import type { ActorExpressionEvaluatorFactory } from '@comunica/bus-expression-evaluator-factory';
import type { MediatorFunctionFactory } from '@comunica/bus-function-factory';
import { createFuncMediator } from '@comunica/bus-function-factory/test/util';
import { KeysInitQuery } from '@comunica/context-entries';
import type { IActionContext } from '@comunica/types';
import type { Bindings } from '@comunica/utils-bindings-factory';
import { SparqlOperator } from '@comunica/utils-expression-evaluator';
import type { AggregateEvaluator, IBindingsAggregator } from '@incremunica/bus-bindings-aggregator-factory';
import { KeysBindings } from '@incremunica/context-entries';
import {
createFuncMediator,
BF,
decimal,
DF,
Expand All @@ -16,12 +20,11 @@ import {
getMockEEFactory,
int,
makeAggregate,
} from '@comunica/utils-expression-evaluator/test/util/helpers';
import type { IBindingsAggregator } from '@incremunica/bus-bindings-aggregator-factory';
} from '@incremunica/dev-tools';
import type * as RDF from '@rdfjs/types';
import { AverageAggregator } from '../lib/AverageAggregator';
import { AverageAggregator } from '../lib';

async function runAggregator(aggregator: IBindingsAggregator, input: RDF.Bindings[]): Promise<RDF.Term | undefined> {
async function runAggregator(aggregator: IBindingsAggregator, input: Bindings[]): Promise<RDF.Term | undefined> {
for (const bindings of input) {
await aggregator.putBindings(bindings);
}
Expand All @@ -46,11 +49,17 @@ async function createAggregator({ expressionEvaluatorFactory, context, distinct,
functionName: SparqlOperator.ADDITION,
requireTermExpression: true,
}),
await mediatorFunctionFactory.mediate({
context,
functionName: SparqlOperator.SUBTRACTION,
requireTermExpression: true,
}),
await mediatorFunctionFactory.mediate({
context,
functionName: SparqlOperator.DIVISION,
requireTermExpression: true,
}),
true,
);
}

Expand All @@ -63,6 +72,7 @@ describe('AverageAggregator', () => {
mediatorFunctionFactory = createFuncMediator([
args => new ActorFunctionFactoryTermAddition(args),
args => new ActorFunctionFactoryTermDivision(args),
args => new ActorFunctionFactoryTermSubtraction(args),
], {});
expressionEvaluatorFactory = getMockEEFactory({
mediatorFunctionFactory,
Expand All @@ -83,50 +93,133 @@ describe('AverageAggregator', () => {
});
});

it('a list of bindings', async() => {
it('a list of bindings 1', async() => {
const input = [
BF.bindings([[ DF.variable('x'), float('1') ]]),
BF.bindings([[ DF.variable('x'), int('2') ]]),
BF.bindings([[ DF.variable('x'), int('3') ]]),
BF.bindings([[ DF.variable('x'), int('4') ]]),
BF.bindings([[ DF.variable('x'), float('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('4') ]]).setContextEntry(KeysBindings.isAddition, true),
];

await expect(runAggregator(aggregator, input)).resolves.toEqual(float('2.5'));
});

it('a list of bindings 2', async() => {
const input = [
BF.bindings([[ DF.variable('x'), float('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('4') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('4') ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, false),
];

await expect(runAggregator(aggregator, input)).resolves.toEqual(float('2'));
});

it('with respect to empty input', async() => {
await expect(runAggregator(aggregator, [])).resolves.toEqual(int('0'));
});

it('with respect to type promotion and subtype substitution', async() => {
it('should error on a deletion if aggregator empty', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, false),
];

await expect(runAggregator(aggregator, input)).rejects.toThrow(
new Error('Cannot remove term "2"^^http://www.w3.org/2001/XMLSchema#integer from empty average aggregator'),
);
});

it('should error on a deletion that has not been added', async() => {
const input = [
BF.bindings([[ DF.variable('x'), DF.literal('1', DF.namedNode('http://www.w3.org/2001/XMLSchema#byte')) ]]),
BF.bindings([[ DF.variable('x'), int('2') ]]),
BF.bindings([[ DF.variable('x'), float('3') ]]),
BF.bindings([[ DF.variable('x'), DF.literal('4', DF.namedNode('http://www.w3.org/2001/XMLSchema#nonNegativeInteger')) ]]),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, false),
];

await expect(runAggregator(aggregator, input)).rejects.toThrow(
new Error('Cannot remove term "2"^^http://www.w3.org/2001/XMLSchema#integer that was not added to average aggregator'),
);
});

it('delete everything', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, false),
];

await expect(runAggregator(aggregator, input)).resolves
.toEqual((<AggregateEvaluator>aggregator).emptyValueTerm());
});

it('with respect to type promotion and subtype substitution 1', async() => {
const input = [
BF.bindings([[ DF.variable('x'), DF.literal('1', DF.namedNode('http://www.w3.org/2001/XMLSchema#byte')) ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), float('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), DF.literal('4', DF.namedNode('http://www.w3.org/2001/XMLSchema#nonNegativeInteger')) ]]).setContextEntry(KeysBindings.isAddition, true),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(float('2.5'));
});

it('with respect to type preservation', async() => {
it('with respect to type promotion and subtype substitution 2', async() => {
const input = [
BF.bindings([[ DF.variable('x'), DF.literal('1', DF.namedNode('http://www.w3.org/2001/XMLSchema#byte')) ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), float('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), DF.literal('4', DF.namedNode('http://www.w3.org/2001/XMLSchema#nonNegativeInteger')) ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), DF.literal('1', DF.namedNode('http://www.w3.org/2001/XMLSchema#byte')) ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), float('3') ]]).setContextEntry(KeysBindings.isAddition, false),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(float('3'));
});

it('with respect to type preservation 1', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('1') ]]),
BF.bindings([[ DF.variable('x'), int('2') ]]),
BF.bindings([[ DF.variable('x'), int('3') ]]),
BF.bindings([[ DF.variable('x'), int('4') ]]),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('4') ]]).setContextEntry(KeysBindings.isAddition, true),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(decimal('2.5'));
});

it('with respect to type promotion 2', async() => {
it('with respect to type preservation 2', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('3') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('4') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, false),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(decimal('3.5'));
});

it('with respect to type promotion 1', async() => {
const input = [
BF.bindings([[ DF.variable('x'), double('1000') ]]),
BF.bindings([[ DF.variable('x'), int('2000') ]]),
BF.bindings([[ DF.variable('x'), float('3000') ]]),
BF.bindings([[ DF.variable('x'), double('4000') ]]),
BF.bindings([[ DF.variable('x'), double('1000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), float('3000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), double('4000') ]]).setContextEntry(KeysBindings.isAddition, true),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(double('2.5E3'));
});

it('with respect to type promotion 2', async() => {
const input = [
BF.bindings([[ DF.variable('x'), double('1000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), float('3000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), double('4000') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), float('3000') ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), double('1000') ]]).setContextEntry(KeysBindings.isAddition, false),
];
await expect(runAggregator(aggregator, input)).resolves.toEqual(double('3.0E3'));
});
});

describe('distinctive avg', () => {
Expand All @@ -141,12 +234,31 @@ describe('AverageAggregator', () => {
});
});

it('a list of bindings', async() => {
it('a list of bindings 1', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([
[ DF.variable('x'), int('1') ],
[ DF.variable('y'), int('1') ],
]).setContextEntry(KeysBindings.isAddition, true),
];

await expect(runAggregator(aggregator, input)).resolves.toEqual(decimal('1.25'));
});

it('a list of bindings 2', async() => {
const input = [
BF.bindings([[ DF.variable('x'), int('1') ]]),
BF.bindings([[ DF.variable('x'), int('2') ]]),
BF.bindings([[ DF.variable('x'), int('1') ]]),
BF.bindings([[ DF.variable('x'), int('1') ], [ DF.variable('y'), int('1') ]]),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('2') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([
[ DF.variable('x'), int('1') ],
[ DF.variable('y'), int('1') ],
]).setContextEntry(KeysBindings.isAddition, true),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, false),
BF.bindings([[ DF.variable('x'), int('1') ]]).setContextEntry(KeysBindings.isAddition, false),
];

await expect(runAggregator(aggregator, input)).resolves.toEqual(decimal('1.5'));
Expand Down
Loading

0 comments on commit a9884ee

Please sign in to comment.