Skip to content

Commit

Permalink
Refactor ConstraintExecutorRegistry to JayveeConstraintExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
georg-schwarz committed Mar 1, 2024
1 parent 3c7bedb commit b429e51
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 125 deletions.
33 changes: 11 additions & 22 deletions apps/docs/docs/dev/12-jayvee-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ These kind of tests are mainly located inside the [language-server](https://gith
The testing utils are located inside the `language-server` in a dedicated [test folder](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/test).
These utils can be imported using `@jvalue/jayvee-language-server/test` and contain the following parts:

[**langium-utils.ts**](https://github.com/jvalue/jayvee/blob/main/libs/language-server/src/test/langium-utils.ts):
[**langium-utils.ts**](https://github.com/jvalue/jayvee/blob/main/libs/language-server/src/test/langium-utils.ts):
This utils file contains two functions:
- `parseHelper` to simplify parsing the input (content of a *.jv file) and returning the corresponding `LangiumDocument`, and
- `validationHelper` parse and validate the created document.
Expand Down Expand Up @@ -100,34 +100,23 @@ pipeline Pipeline {
### Existing tests
Currently there are already tests for the following parts:
- Language-server validation checks (located [here](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/validation))
- Language-server constraint validation (located [here](https://github.com/jvalue/jayvee/tree/dev/libs/language-server/src/lib/constraint))
- Custom block (property) validation of the three existing extensions (std extension located [here](https://github.com/jvalue/jayvee/blob/dev/libs/extensions/std/lang/src))
- Grammar validation tests for all official full examples from the [/example](https://github.com/jvalue/jayvee/tree/main/example) folder (located [here](https://github.com/jvalue/jayvee/blob/dev/libs/extensions/std/lang/src/example-validation.spec.ts))
- Grammar validation tests for all block examples of the std extension (located [here](https://github.com/jvalue/jayvee/blob/dev/libs/extensions/std/lang/src/meta-inf-example-validation.spec.ts))
- Language-server constraint validation (located [here](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/constraint))
- Custom block (property) validation of the three existing extensions (std extension located [here](https://github.com/jvalue/jayvee/blob/main/libs/extensions/std/lang/src))
- Grammar validation tests for all official full examples from the [/example](https://github.com/jvalue/jayvee/tree/main/example) folder (located [here](https://github.com/jvalue/jayvee/blob/main/libs/extensions/std/lang/src/example-validation.spec.ts))
- Grammar validation tests for all block examples of the std extension (located [here](https://github.com/jvalue/jayvee/blob/main/libs/extensions/std/lang/src/meta-inf-example-validation.spec.ts))

## Execution tests
These kind of tests are mainly located inside the [interpreter](https://github.com/jvalue/jayvee/tree/main/libs/language-server), the [interpreter-lib](https://github.com/jvalue/jayvee/tree/dev/libs/interpreter-lib), the [execution lib](https://github.com/jvalue/jayvee/tree/dev/libs/execution) as well as the execution parts of each extension (for example [std/exec](https://github.com/jvalue/jayvee/tree/main/libs/extensions/std/exec)).
These kind of tests are mainly located inside the [interpreter](https://github.com/jvalue/jayvee/tree/main/libs/language-server), the [interpreter-lib](https://github.com/jvalue/jayvee/tree/main/libs/interpreter-lib), the [execution lib](https://github.com/jvalue/jayvee/tree/main/libs/execution) as well as the execution parts of each extension (for example [std/exec](https://github.com/jvalue/jayvee/tree/main/libs/extensions/std/exec)).

### Testing utils
The testing utils for execution tests are spread between the extensions, with the interfaces and base utils located inside the [execution lib](https://github.com/jvalue/jayvee/tree/dev/libs/execution).
The testing utils for execution tests are spread between the extensions, with the interfaces and base utils located inside the [execution lib](https://github.com/jvalue/jayvee/tree/main/libs/execution).
They can be imported using `@jvalue/jayvee-extensions/rdbms/test`, `@jvalue/jayvee-extensions/std/test` and `@jvalue/jayvee-execution/test`.

[**utils.ts**](https://github.com/jvalue/jayvee/blob/dev/libs/execution/test/utils.ts):
At the moment this only contains two functions:
- `clearBlockExecutorRegistry` for clearing the registry containing all `BlockExecutor`s, and
- `clearConstraintExecutorRegistry` clearing the corresponding `ConstraintExecutor`s registry.
They are required in case the tested method initializes Jayvee itself (see [smoke test](#existing-tests-1)).

[**test-logger.ts**](https://github.com/jvalue/jayvee/blob/dev/libs/execution/test/test-logger.ts):
This contains a subclass of the [`DefaultLogger`](https://github.com/jvalue/jayvee/blob/dev/libs/execution/src/lib/logging/default-logger.ts) used for tests which require a `Logger` implementation. The `TestLogger` contains the following tests functionality:
- `getLogs`: retrieve the cached logs that the logger received.
- `clearLogs`: clear the cached logs.

[**block-executor-mocks.ts**](https://github.com/jvalue/jayvee/blob/dev/libs/execution/test/block-executor-mock.ts):
[**block-executor-mocks.ts**](https://github.com/jvalue/jayvee/blob/main/libs/execution/test/block-executor-mock.ts):
`BlockExecutorMock` interface for defining mocks for `AbstractBlockExecutor`. Generally only loader and executor blocks require mocks, because they interact with "the outside world" (i.e. `HttpExtractor` making http calls).
Due to how vastly different each `BlockExecutor` can be, this interface is very simple, containing only a `setup(...args: unknown[])` and a `restore()` method. See below for existing implementations.

[**rdbms/exec/test**](https://github.com/jvalue/jayvee/tree/dev/libs/extensions/rdbms/exec/test):
[**rdbms/exec/test**](https://github.com/jvalue/jayvee/tree/main/libs/extensions/rdbms/exec/test):
Contains the implementation of `BlockExecutorMock` for `PostgresLoaderExecutor` and `SQLiteLoaderExecutor`.
Both of these executors are mocked using `jest.mock` to mock the corresponding libraries (`pg` and `sqlite3`)
**Usage:**
Expand Down Expand Up @@ -187,7 +176,7 @@ describe('Dummy describe', () => {
});
```

[**std/exec/test/mocks**](https://github.com/jvalue/jayvee/tree/dev/libs/extensions/std/exec/test):
[**std/exec/test/mocks**](https://github.com/jvalue/jayvee/tree/main/libs/extensions/std/exec/test):
Contains the implementation of `BlockExecutorMock` for `HttpExtractorExecutorMock`.
This implementation uses [nock](https://www.npmjs.com/package/nock) for mocking HTTP(S) responses.
The `setup` method is further specified requiring one parameter `registerMocks: () => Array<nock.Scope>`, which returns all used `nock.Scope` (i.e. the return value of `nock('<URL>')`), see usage below:
Expand Down Expand Up @@ -251,4 +240,4 @@ describe('Dummy describe', () => {

### Existing tests
Currently there are already tests for the following parts:
- Smoke test for official examples (located [here](https://github.com/jvalue/jayvee/blob/dev/apps/interpreter/src/examples-smoke-test.spec.ts))
- Smoke test for official examples (located [here](https://github.com/jvalue/jayvee/blob/main/apps/interpreter/src/examples-smoke-test.spec.ts))
8 changes: 1 addition & 7 deletions apps/interpreter/src/examples-smoke-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

import * as path from 'path';

import {
clearConstraintExecutorRegistry,
processExitMockImplementation,
} from '@jvalue/jayvee-execution/test';
import { processExitMockImplementation } from '@jvalue/jayvee-execution/test';
import {
PostgresLoaderExecutorMock,
SQLiteLoaderExecutorMock,
Expand Down Expand Up @@ -71,9 +68,6 @@ describe('jv example smoke tests', () => {
httpExtractorMock.restore();
postgresLoaderMock.restore();
sqliteLoaderMock.restore();

// Clear registries
clearConstraintExecutorRegistry();
});

it('should have no errors when executing cars.jv example', async () => {
Expand Down
4 changes: 0 additions & 4 deletions apps/interpreter/src/parse-only.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as fs from 'node:fs';
import * as path from 'path';
import * as process from 'process';

import { clearConstraintExecutorRegistry } from '@jvalue/jayvee-execution/test';
import {
RunOptions,
interpretModel,
Expand Down Expand Up @@ -49,9 +48,6 @@ describe('Parse Only', () => {
jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error();
});

// Reset jayvee specific stuff
clearConstraintExecutorRegistry();
});

it('should exit with 0 on a valid option', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';

import {
ConstraintDefinition,
Registry,
isExpressionConstraintDefinition,
isTypedConstraintDefinition,
} from '@jvalue/jayvee-language-server';
import { assertUnreachable } from 'langium';

import { ConstraintExecutor } from './constraint-executor';
import { AllowlistConstraintExecutor } from './executors/allowlist-constraint-executor';
import { DenylistConstraintExecutor } from './executors/denylist-constraint-executor';
import { ExpressionConstraintExecutor } from './executors/expression-constraint-executor';
import { LengthConstraintExecutor } from './executors/length-constraint-executor';
import { RangeConstraintExecutor } from './executors/range-constraint-executor';
import { RegexConstraintExecutor } from './executors/regex-constraint-executor';
import { TypedConstraintExecutorClass } from './typed-constraint-executor-class';

export interface JayveeConstraintExtension {
registerConstraintExecutor(executorClass: TypedConstraintExecutorClass): void;

getConstraintExecutors(): TypedConstraintExecutorClass<ConstraintExecutor>[];

createConstraintExecutor(
constraint: ConstraintDefinition,
): ConstraintExecutor;
}

export class DefaultConstraintExtension
extends Registry<TypedConstraintExecutorClass>
implements JayveeConstraintExtension
{
constructor() {
super();

this.registerConstraintExecutor(AllowlistConstraintExecutor);
this.registerConstraintExecutor(DenylistConstraintExecutor);
this.registerConstraintExecutor(RegexConstraintExecutor);
this.registerConstraintExecutor(LengthConstraintExecutor);
this.registerConstraintExecutor(RangeConstraintExecutor);
}

registerConstraintExecutor(executorClass: TypedConstraintExecutorClass) {
this.register(executorClass.type, executorClass);
}

getConstraintExecutors() {
return this.getAll();
}

createConstraintExecutor(
constraint: ConstraintDefinition,
): ConstraintExecutor {
if (isTypedConstraintDefinition(constraint)) {
const constraintType = constraint.type.ref?.name;
assert(
constraintType !== undefined,
`Could not resolve reference to constraint type of ${constraint.name}`,
);
const constraintExecutor = this.get(constraintType);
assert(
constraintExecutor !== undefined,
`No executor was registered for constraint type ${constraintType}`,
);

return new constraintExecutor();
} else if (isExpressionConstraintDefinition(constraint)) {
return new ExpressionConstraintExecutor(constraint);
}
assertUnreachable(constraint);
}
}
65 changes: 0 additions & 65 deletions libs/execution/src/lib/constraints/constraint-executor-registry.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,22 @@ import {
} from '@jvalue/jayvee-language-server';
import { NodeFileSystem } from 'langium/node';

import {
getRegisteredConstraintExecutors,
registerDefaultConstraintExecutors,
} from './constraint-executor-registry';
import { DefaultConstraintExtension } from './constraint-executor-extension';

describe('default constraint executors', () => {
describe('default constraint extension', () => {
it('should include executors for all constraint types', async () => {
// Create language services
const services = createJayveeServices(NodeFileSystem).Jayvee;
await initializeWorkspace(services);

registerDefaultConstraintExecutors();
const defaultConstraintExtension = new DefaultConstraintExtension();

getAllBuiltinConstraintTypes(
services.shared.workspace.LangiumDocuments,
).forEach((constraintType) => {
const matchingConstraintExecutorClass =
getRegisteredConstraintExecutors().find(
(c) => c.type === constraintType.type,
);
const matchingConstraintExecutorClass = defaultConstraintExtension
.getConstraintExecutors()
.find((c) => c.type === constraintType.type);

expect(matchingConstraintExecutorClass).toBeDefined();
});
Expand Down
2 changes: 1 addition & 1 deletion libs/execution/src/lib/constraints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

export * from './constraint-executor-registry';
export * from './constraint-executor-extension';
2 changes: 2 additions & 0 deletions libs/execution/src/lib/execution-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@jvalue/jayvee-language-server';
import { assertUnreachable, isReference } from 'langium';

import { JayveeConstraintExtension } from './constraints';
import {
DebugGranularity,
DebugTargets,
Expand All @@ -43,6 +44,7 @@ export class ExecutionContext {
constructor(
public readonly pipeline: PipelineDefinition,
public readonly executionExtension: JayveeExecExtension,
public readonly constraintExtension: JayveeConstraintExtension,
public readonly logger: Logger,
public readonly runOptions: {
isDebugMode: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
ValuetypeVisitor,
} from '@jvalue/jayvee-language-server';

import { createConstraintExecutor } from '../../constraints/constraint-executor-registry';
import { type ExecutionContext } from '../../execution-context';

export function isValidValueRepresentation(
Expand Down Expand Up @@ -53,7 +52,8 @@ class ValueRepresentationValidityVisitor extends ValuetypeVisitor<boolean> {
this.context.evaluationContext,
);
for (const constraint of constraints) {
const constraintExecutor = createConstraintExecutor(constraint);
const constraintExecutor =
this.context.constraintExtension.createConstraintExecutor(constraint);

this.context.enterNode(constraint);
const valueFulfilledConstraint = constraintExecutor.isValid(
Expand Down
7 changes: 2 additions & 5 deletions libs/execution/test/utils/test-infrastructure-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
CachedLogger,
DebugGranularity,
DebugTargets,
DefaultConstraintExtension,
ExecutionContext,
JayveeExecExtension,
StackNode,
Table,
TableColumn,
constraintExecutorRegistry,
} from '../../src';

export class TestExecExtension extends JayveeExecExtension {
Expand All @@ -28,10 +28,6 @@ export class TestExecExtension extends JayveeExecExtension {
}
}

export function clearConstraintExecutorRegistry() {
constraintExecutorRegistry.clear();
}

export function processExitMockImplementation(code?: number) {
if (code === undefined || code === 0) {
return undefined as never;
Expand Down Expand Up @@ -62,6 +58,7 @@ export function getTestExecutionContext(
const executionContext = new ExecutionContext(
pipeline,
new TestExecExtension(),
new DefaultConstraintExtension(),
new CachedLogger(runOptions.isDebugMode, undefined, loggerPrintLogs),
runOptions,
new EvaluationContext(new RuntimeParameterProvider()),
Expand Down
Loading

0 comments on commit b429e51

Please sign in to comment.