Skip to content

Commit

Permalink
basic function test support (#2501)
Browse files Browse the repository at this point in the history
  • Loading branch information
MauricioUyaguari authored Dec 15, 2023
1 parent 9676e40 commit b110eaf
Show file tree
Hide file tree
Showing 38 changed files with 2,562 additions and 86 deletions.
5 changes: 5 additions & 0 deletions legend-engine-config/legend-engine-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,11 @@
<artifactId>legend-engine-test-runner-mapping</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-test-runner-function</artifactId>
<scope>runtime</scope>
</dependency>
<!-- RUNTIME -->

<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2023 Goldman Sachs
//
// 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.

package org.finos.legend.engine.language.pure.compiler.toPureGraph;

import org.eclipse.collections.api.RichIterable;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.utility.ListIterate;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.data.EmbeddedDataFirstPassBuilder;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.test.TestBuilderHelper;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.test.assertion.TestAssertionFirstPassBuilder;
import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.ParameterValue;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.function.FunctionTest;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.function.FunctionTestSuite;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.function.StoreTestData;
import org.finos.legend.engine.shared.core.operational.Assert;
import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException;
import org.finos.legend.pure.generated.Root_meta_external_store_model_ModelStore_Impl;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_FunctionTest;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_FunctionTestSuite;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_FunctionTestSuite_Impl;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_FunctionTest_Impl;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_ParameterValue;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_ParameterValue_Impl;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_StoreTestData;
import org.finos.legend.pure.generated.Root_meta_legend_function_metamodel_StoreTestData_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_test_AtomicTest;
import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.ConcreteFunctionDefinition;

public class HelperFunctionBuilder
{
public static void processFunctionSuites(Function func, ConcreteFunctionDefinition<?> metamodelFunction, CompileContext compileContext, ProcessingContext ctx)
{
if (func.tests != null && !func.tests.isEmpty())
{
TestBuilderHelper.validateTestSuiteIdsList(func.tests, func.sourceInformation);
metamodelFunction._tests(ListIterate.collect(func.tests, suite -> buildFunctionTestSuites(metamodelFunction, suite, compileContext, ctx)));
}
}

static org.finos.legend.pure.m3.coreinstance.meta.pure.test.Test buildFunctionTestSuites(ConcreteFunctionDefinition<?> metamodelFunction, org.finos.legend.engine.protocol.pure.v1.model.test.Test test, CompileContext compileContext, ProcessingContext processingContext
)
{
if (test instanceof FunctionTestSuite)
{
// validate tests and test suite ids
FunctionTestSuite testSuite = (FunctionTestSuite) test;
TestBuilderHelper.validateNonEmptySuite(testSuite);
TestBuilderHelper.validateTestIds(testSuite.tests, testSuite.sourceInformation);
Root_meta_legend_function_metamodel_FunctionTestSuite metamodelSuite = new Root_meta_legend_function_metamodel_FunctionTestSuite_Impl("",null, compileContext.pureModel.getClass("meta::legend::function::metamodel::FunctionTestSuite"));
if (testSuite.testData != null && !testSuite.testData.isEmpty())
{
TestBuilderHelper.validateIds(ListIterate.collect(testSuite.testData, testData -> testData.store), testSuite.sourceInformation, "Multiple test data found for stores");
RichIterable<? extends org.finos.legend.pure.generated.Root_meta_core_runtime_Runtime> runtimes;
// TODO: we can remove some of these checks once we support these use cases
try
{
runtimes = org.finos.legend.pure.generated.core_pure_corefunctions_metaExtension.Root_meta_pure_functions_meta_extractRuntimesFromFunctionDefinition_FunctionDefinition_1__Runtime_MANY_(metamodelFunction, compileContext.pureModel.getExecutionSupport());
}
catch (Exception error)
{
throw new EngineException("Unable to extract runtime from function which test data is provided for. Test Data is only supported to be provided for runtimes", testSuite.sourceInformation, EngineErrorType.COMPILATION, error);
}
if (runtimes.isEmpty())
{
throw new EngineException("Function test data requires a function to have one runtime: No runtimes found in function." + metamodelFunction.getName(), testSuite.sourceInformation, EngineErrorType.COMPILATION);
}
if (runtimes.size() > 1)
{
throw new EngineException("Function test data requires a function to have one runtime. Found " + runtimes.size() + " runtimes in function " + metamodelFunction.getName(), testSuite.sourceInformation, EngineErrorType.COMPILATION);
}
org.finos.legend.pure.generated.Root_meta_core_runtime_Runtime runtime = runtimes.getOnly();
metamodelSuite._testData(ListIterate.collect(testSuite.testData, storeData -> buildFunctionTestData(runtime, storeData, compileContext, processingContext)));
}
metamodelSuite
._id(testSuite.id)
._tests(ListIterate.collect(testSuite.tests, unitTest -> (Root_meta_pure_test_AtomicTest) buildFunctionTestSuites(metamodelFunction, unitTest, compileContext, processingContext)))
._testable(metamodelFunction);
return metamodelSuite;
}
else if (test instanceof FunctionTest)
{
FunctionTest functionTest = (FunctionTest) test;
Root_meta_legend_function_metamodel_FunctionTest metamodelTest = new Root_meta_legend_function_metamodel_FunctionTest_Impl("",null, compileContext.pureModel.getClass("meta::legend::function::metamodel::FunctionTest"))
._id(functionTest.id);
if (functionTest.parameters != null && !functionTest.parameters.isEmpty())
{
metamodelTest._parameters(ListIterate.collect(functionTest.parameters, param -> processFunctionTestParameterValue(param, compileContext)));
}
TestBuilderHelper.validateNonEmptyTest(functionTest);
if (functionTest.assertions.size() > 1)
{
throw new EngineException("Function test only support one assertion", test.sourceInformation, EngineErrorType.COMPILATION);
}
metamodelTest._assertions(ListIterate.collect(functionTest.assertions, assertion -> assertion.accept(new TestAssertionFirstPassBuilder(compileContext, processingContext))));
return metamodelTest;
}
return null;
}

private static Root_meta_legend_function_metamodel_StoreTestData buildFunctionTestData(org.finos.legend.pure.generated.Root_meta_core_runtime_Runtime runtime, StoreTestData storeTestData, CompileContext compileContext, ProcessingContext ctx)
{
Root_meta_legend_function_metamodel_StoreTestData_Impl metamodelStoreTestData = new Root_meta_legend_function_metamodel_StoreTestData_Impl("", null, compileContext.pureModel.getClass("meta::legend::function::metamodel::StoreTestData"));
org.finos.legend.pure.m3.coreinstance.meta.pure.store.Store resolvedStore = null;
if (storeTestData.store.equals("ModelStore"))
{
resolvedStore = new Root_meta_external_store_model_ModelStore_Impl("");
}
else
{
resolvedStore = compileContext.resolveStore(storeTestData.store, storeTestData.sourceInformation);
}
try
{
org.finos.legend.pure.generated.Root_meta_core_runtime_Connection connection = runtime.connectionByElement(resolvedStore, compileContext.pureModel.getExecutionSupport());
Assert.assertTrue(connection != null, () -> "connection not found");
}
catch (Exception exception)
{
// throw new EngineException("Store '" + storeTestData.store + "' not specified in the runtime in the function", storeTestData.sourceInformation, EngineErrorType.COMPILATION, exception);
}
metamodelStoreTestData._data(storeTestData.data.accept(new EmbeddedDataFirstPassBuilder(compileContext, ctx)));
metamodelStoreTestData._store(resolvedStore);
metamodelStoreTestData._doc(storeTestData.doc);
return metamodelStoreTestData;
}

private static Root_meta_legend_function_metamodel_ParameterValue processFunctionTestParameterValue(ParameterValue parameterValue, CompileContext context)
{
Root_meta_legend_function_metamodel_ParameterValue pureParameterValue = new Root_meta_legend_function_metamodel_ParameterValue_Impl("", null, context.pureModel.getClass("meta::legend::function::metamodel::ParameterValue"));
pureParameterValue._name(parameterValue.name);
pureParameterValue._value(Lists.immutable.with(parameterValue.value.accept(new ValueSpecificationBuilder(context, Lists.mutable.empty(), new ProcessingContext("")))));
return pureParameterValue;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.eclipse.collections.impl.list.mutable.FastList;
import org.eclipse.collections.impl.utility.ListIterate;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.handlers.Handlers;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.test.TestBuilderHelper;
import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Multiplicity;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.AssociationMapping;
Expand All @@ -51,6 +52,9 @@
import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.Variable;
import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda;
import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException;
import org.finos.legend.pure.generated.Root_meta_external_store_model_ModelStore_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_data_StoreTestData;
import org.finos.legend.pure.generated.Root_meta_pure_data_StoreTestData_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_EnumValueMapping_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_EnumerationMapping_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_MappingClass_Impl;
Expand All @@ -60,16 +64,13 @@
import org.finos.legend.pure.generated.Root_meta_pure_mapping_aggregationAware_GroupByFunctionSpecification_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_metamodel_MappingTestSuite;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_metamodel_MappingTestSuite_Impl;
import org.finos.legend.pure.generated.Root_meta_external_store_model_ModelStore_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_mapping_xStore_XStoreAssociationImplementation_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_metamodel_function_LambdaFunction_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_metamodel_function_property_Property_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_metamodel_relationship_Generalization_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_metamodel_type_generics_GenericType_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_metamodel_valuespecification_VariableExpression_Impl;
import org.finos.legend.pure.generated.Root_meta_pure_test_AtomicTest;
import org.finos.legend.pure.generated.Root_meta_pure_data_StoreTestData;
import org.finos.legend.pure.generated.Root_meta_pure_data_StoreTestData_Impl;
import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.AssociationImplementation;
import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.InstanceSetImplementation;
import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.Mapping;
Expand All @@ -94,7 +95,6 @@
import org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement;
import org.finos.legend.pure.m4.coreinstance.SourceInformation;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -510,50 +510,21 @@ public static Test processMappingTestAndTestSuite(org.finos.legend.engine.protoc
if (test instanceof MappingTestSuite)
{
// validate tests and test suite ids
MappingTestSuite testSuite = (MappingTestSuite) test;
if (testSuite.tests == null || testSuite.tests.isEmpty())
{
throw new EngineException("Mapping TestSuites should have at least 1 test", testSuite.sourceInformation, EngineErrorType.COMPILATION);
}
List<String> testIds = ListIterate.collect(testSuite.tests, t -> t.id);
List<String> duplicateTestIds = testIds.stream().filter(e -> Collections.frequency(testIds, e) > 1).distinct().collect(Collectors.toList());
if (!duplicateTestIds.isEmpty())
{
throw new EngineException("Multiple tests found with ids : '" + String.join(",", duplicateTestIds) + "'", testSuite.sourceInformation, EngineErrorType.COMPILATION);
}
if (test instanceof MappingTestSuite)
{

MappingTestSuite queryTestSuite = (MappingTestSuite) test;
Root_meta_pure_mapping_metamodel_MappingTestSuite compiledMappingSuite = new Root_meta_pure_mapping_metamodel_MappingTestSuite_Impl("", null, context.pureModel.getClass("meta::pure::mapping::metamodel::MappingTestSuite"));

return compiledMappingSuite._id(queryTestSuite.id)
._query(HelperValueSpecificationBuilder.buildLambda(queryTestSuite.func, context))
._tests(ListIterate.collect(queryTestSuite.tests, unitTest -> (Root_meta_pure_test_AtomicTest) HelperMappingBuilder.processMappingTestAndTestSuite(unitTest, pureMapping, context)))
._testable(pureMapping);
}
else
{
throw new EngineException("Unsupported Mapping Test Suite", testSuite.sourceInformation, EngineErrorType.COMPILATION);
}
MappingTestSuite queryTestSuite = (MappingTestSuite) test;
TestBuilderHelper.validateNonEmptySuite(queryTestSuite);
TestBuilderHelper.validateTestIds(queryTestSuite.tests, queryTestSuite.sourceInformation);
Root_meta_pure_mapping_metamodel_MappingTestSuite compiledMappingSuite = new Root_meta_pure_mapping_metamodel_MappingTestSuite_Impl("", null, context.pureModel.getClass("meta::pure::mapping::metamodel::MappingTestSuite"));
return compiledMappingSuite._id(queryTestSuite.id)
._query(HelperValueSpecificationBuilder.buildLambda(queryTestSuite.func, context))
._tests(ListIterate.collect(queryTestSuite.tests, unitTest -> (Root_meta_pure_test_AtomicTest) HelperMappingBuilder.processMappingTestAndTestSuite(unitTest, pureMapping, context)))
._testable(pureMapping);
}
else if (test instanceof MappingTest)
{
MappingTest mappingTest = (MappingTest) test;
Root_meta_pure_test_AtomicTest pureMappingTest = (Root_meta_pure_test_AtomicTest) TestCompilerHelper.compilePureMappingTests(mappingTest, context, new ProcessingContext("Mapping Test '" + mappingTest.id + "' Second Pass"));
if (mappingTest.assertions == null || mappingTest.assertions.isEmpty())
{
throw new EngineException("Mapping Tests should have at least 1 assert", mappingTest.sourceInformation, EngineErrorType.COMPILATION);
}

List<String> assertionIds = ListIterate.collect(mappingTest.assertions, a -> a.id);
List<String> duplicateAssertionIds = assertionIds.stream().filter(e -> Collections.frequency(assertionIds, e) > 1).distinct().collect(Collectors.toList());

if (!duplicateAssertionIds.isEmpty())
{
throw new EngineException("Multiple assertions found with ids : '" + String.join(",", duplicateAssertionIds) + "'", mappingTest.sourceInformation, EngineErrorType.COMPILATION);
}

TestBuilderHelper.validateNonEmptyTest(mappingTest);
TestBuilderHelper.validateAssertionIds(mappingTest.assertions, mappingTest.sourceInformation);
pureMappingTest._assertions(ListIterate.collect(mappingTest.assertions, assertion -> context.getCompilerExtensions().getExtraTestAssertionProcessors().stream()
.map(processor -> processor.value(assertion, context, new ProcessingContext("Test Assertion '" + assertion.id + "'")))
.filter(Objects::nonNull)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.utility.ListIterate;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.test.TestBuilderHelper;
import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElementVisitor;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection;
Expand Down Expand Up @@ -169,13 +170,7 @@ public PackageableElement visit(Mapping mapping)
}
if (mapping.testSuites != null)
{
List<String> testSuiteIds = ListIterate.collect(mapping.testSuites, suite -> suite.id);
List<String> duplicateTestSuiteIds = testSuiteIds.stream().filter(e -> Collections.frequency(testSuiteIds, e) > 1).distinct().collect(Collectors.toList());

if (!duplicateTestSuiteIds.isEmpty())
{
throw new EngineException("Multiple testSuites found with ids : '" + String.join(",", duplicateTestSuiteIds) + "'", mapping.sourceInformation, EngineErrorType.COMPILATION);
}
TestBuilderHelper.validateTestSuiteIdsList(mapping.testSuites, mapping.sourceInformation);
pureMapping._tests(ListIterate.collect(mapping.testSuites, suite -> HelperMappingBuilder.processMappingTestAndTestSuite(suite, pureMapping, this.context)));
}
return pureMapping;
Expand Down
Loading

0 comments on commit b110eaf

Please sign in to comment.