-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: data-driven tests for partial evaluation (#578)
Closes partially #433. ### Summary of Changes * Implement a data-driven way to test the partial evaluator * Add a very basic port of our implementation from Xtext and lay out the general structure of the full implementation --------- Co-authored-by: megalinter-bot <[email protected]>
- Loading branch information
1 parent
2071012
commit 2e6be9f
Showing
20 changed files
with
1,213 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Partial Evaluation Testing | ||
|
||
Partial evaluation tests are data-driven instead of being specified explicitly. This document explains how to add a new | ||
partial evaluation test. | ||
|
||
## Adding a partial evaluation test | ||
|
||
1. Create a new **folder** (not just a file!) in the `tests/resources/partial evaluation` directory or any subdirectory. | ||
Give the folder a descriptive name, since the folder name becomes part of the test name. | ||
|
||
!!! tip "Skipping a test" | ||
|
||
If you want to skip a test, add the prefix `skip-` to the folder name. | ||
|
||
2. Add files with the extension `.sdstest` **directly inside the folder**. All files in a folder will be loaded into the | ||
same workspace, so they can reference each other. Files in different folders are loaded into different workspaces, so | ||
they cannot reference each other. | ||
3. Add the Safe-DS code that you want to test to the file. | ||
4. Surround entire nodes whose value you want to check with test markers, e.g. `1 + 2`. | ||
5. For each pair of test markers, add a test comment with one of the formats listed below. Test comments and test | ||
markers are mapped to each other by their position in the file, i.e. the first test comment corresponds to the first | ||
test marker, the second test comment corresponds to the second test marker, etc. | ||
* `// $TEST$ constant equivalence_class <id>`: Assert that all nodes with the same `<id>` get partially evaluated | ||
successfully to the same constant expression. | ||
* `// $TEST$ constant serialization <value>`: Assert that the node gets partially evaluated to a constant expression | ||
that serializes to `<value>`. | ||
* `// $TEST$ not constant`: Assert that the node cannot be evaluated to a constant expression. | ||
6. Run the tests. The test runner will automatically pick up the new test. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { convertString, CstNode, DefaultValueConverter, GrammarAST, ValueType } from 'langium'; | ||
|
||
export class SafeDsValueConverter extends DefaultValueConverter { | ||
protected override runConverter(rule: GrammarAST.AbstractRule, input: string, cstNode: CstNode): ValueType { | ||
switch (rule.name.toUpperCase()) { | ||
case 'TEMPLATE_STRING_START': | ||
return convertString(input.substring(0, input.length - 1)); | ||
case 'TEMPLATE_STRING_INNER': | ||
return convertString(input.substring(1, input.length - 1)); | ||
case 'TEMPLATE_STRING_END': | ||
return convertString(input.substring(1)); | ||
default: | ||
return super.runConverter(rule, input, cstNode); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import { | ||
isSdsAbstractResult, | ||
SdsAbstractResult, | ||
SdsBlockLambdaResult, | ||
SdsEnumVariant, | ||
SdsExpression, | ||
SdsParameter, | ||
SdsReference, | ||
SdsResult, | ||
} from '../generated/ast.js'; | ||
|
||
/* c8 ignore start */ | ||
export type ParameterSubstitutions = Map<SdsParameter, SdsSimplifiedExpression | undefined>; | ||
export type ResultSubstitutions = Map<SdsAbstractResult, SdsSimplifiedExpression | undefined>; | ||
|
||
export abstract class SdsSimplifiedExpression { | ||
/** | ||
* Removes any unnecessary containers from the expression. | ||
*/ | ||
unwrap(): SdsSimplifiedExpression { | ||
return this; | ||
} | ||
} | ||
|
||
export abstract class SdsIntermediateExpression extends SdsSimplifiedExpression {} | ||
|
||
export abstract class SdsIntermediateCallable extends SdsIntermediateExpression {} | ||
|
||
export class SdsIntermediateBlockLambda extends SdsIntermediateCallable { | ||
constructor( | ||
readonly parameters: SdsParameter[], | ||
readonly results: SdsBlockLambdaResult[], | ||
readonly substitutionsOnCreation: ParameterSubstitutions, | ||
) { | ||
super(); | ||
} | ||
} | ||
|
||
export class SdsIntermediateExpressionLambda extends SdsIntermediateCallable { | ||
constructor( | ||
readonly parameters: SdsParameter[], | ||
readonly result: SdsExpression, | ||
readonly substitutionsOnCreation: ParameterSubstitutions, | ||
) { | ||
super(); | ||
} | ||
} | ||
|
||
export class SdsIntermediateStep extends SdsIntermediateCallable { | ||
constructor( | ||
readonly parameters: SdsParameter[], | ||
readonly results: SdsResult[], | ||
) { | ||
super(); | ||
} | ||
} | ||
|
||
export class SdsIntermediateRecord extends SdsIntermediateExpression { | ||
constructor(readonly resultSubstitutions: ResultSubstitutions) { | ||
super(); | ||
} | ||
|
||
getSubstitutionByReferenceOrNull(reference: SdsReference): SdsSimplifiedExpression | null { | ||
const referencedDeclaration = reference.declaration; | ||
if (!isSdsAbstractResult(referencedDeclaration)) { | ||
return null; | ||
} | ||
|
||
return this.resultSubstitutions.get(referencedDeclaration) ?? null; | ||
} | ||
|
||
getSubstitutionByIndexOrNull(index: number | null): SdsSimplifiedExpression | null { | ||
if (index === null) { | ||
return null; | ||
} | ||
return Array.from(this.resultSubstitutions.values())[index] ?? null; | ||
} | ||
|
||
/** | ||
* If the record contains exactly one substitution its value is returned. Otherwise, it returns `this`. | ||
*/ | ||
override unwrap(): SdsSimplifiedExpression { | ||
if (this.resultSubstitutions.size === 1) { | ||
return this.resultSubstitutions.values().next().value; | ||
} else { | ||
return this; | ||
} | ||
} | ||
|
||
override toString(): string { | ||
const entryString = Array.from(this.resultSubstitutions, ([result, value]) => `${result.name}=${value}`).join( | ||
', ', | ||
); | ||
return `{${entryString}}`; | ||
} | ||
} | ||
|
||
export class SdsIntermediateVariadicArguments extends SdsIntermediateExpression { | ||
constructor(readonly arguments_: (SdsSimplifiedExpression | null)[]) { | ||
super(); | ||
} | ||
|
||
getArgumentByIndexOrNull(index: number | null): SdsSimplifiedExpression | null { | ||
if (index === null) { | ||
return null; | ||
} | ||
return this.arguments_[index] ?? null; | ||
} | ||
} | ||
|
||
export abstract class SdsConstantExpression extends SdsSimplifiedExpression { | ||
abstract equals(other: SdsConstantExpression): boolean; | ||
|
||
abstract override toString(): string; | ||
|
||
/** | ||
* Returns the string representation of the constant expression if it occurs in a string template. | ||
*/ | ||
toInterpolationString(): string { | ||
return this.toString(); | ||
} | ||
} | ||
|
||
export class SdsConstantBoolean extends SdsConstantExpression { | ||
constructor(readonly value: boolean) { | ||
super(); | ||
} | ||
|
||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantBoolean && this.value === other.value; | ||
} | ||
|
||
toString(): string { | ||
return this.value.toString(); | ||
} | ||
} | ||
|
||
export class SdsConstantEnumVariant extends SdsConstantExpression { | ||
constructor(readonly value: SdsEnumVariant) { | ||
super(); | ||
} | ||
|
||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantEnumVariant && this.value === other.value; | ||
} | ||
|
||
toString(): string { | ||
return this.value.name; | ||
} | ||
} | ||
|
||
export abstract class SdsConstantNumber extends SdsConstantExpression {} | ||
|
||
export class SdsConstantFloat extends SdsConstantNumber { | ||
constructor(readonly value: number) { | ||
super(); | ||
} | ||
|
||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantFloat && this.value === other.value; | ||
} | ||
|
||
toString(): string { | ||
return this.value.toString(); | ||
} | ||
} | ||
|
||
export class SdsConstantInt extends SdsConstantNumber { | ||
constructor(readonly value: bigint) { | ||
super(); | ||
} | ||
|
||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantInt && this.value === other.value; | ||
} | ||
|
||
toString(): string { | ||
return this.value.toString(); | ||
} | ||
} | ||
|
||
export class SdsConstantNull extends SdsConstantExpression { | ||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantNull; | ||
} | ||
|
||
toString(): string { | ||
return 'null'; | ||
} | ||
} | ||
|
||
export class SdsConstantString extends SdsConstantExpression { | ||
constructor(readonly value: string) { | ||
super(); | ||
} | ||
|
||
equals(other: SdsConstantExpression): boolean { | ||
return other instanceof SdsConstantString && this.value === other.value; | ||
} | ||
|
||
toString(): string { | ||
return `"${this.value}"`; | ||
} | ||
|
||
override toInterpolationString(): string { | ||
return this.value; | ||
} | ||
} | ||
|
||
/* c8 ignore stop */ |
Oops, something went wrong.