@finos/legend-application-studio
Version:
Legend Studio application core
517 lines • 21.2 kB
JavaScript
/**
* Copyright (c) 2020-present, 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.
*/
import { AssertionStatus, RunTestsTestableInput, TestSuite, AtomicTest, UniqueTestId, TestError, TestExecutionStatus, TestExecuted, AssertPass, AssertFail, PackageableElement, getNullableIDFromTestable, MultiExecutionServiceTestResult, } from '@finos/legend-graph';
import { assertErrorThrown, isNonNullable, ActionState, uuid, assertTrue, guaranteeNonNullable, UnsupportedOperationError, filterByType, } from '@finos/legend-shared';
import { action, flow, makeObservable, observable } from 'mobx';
import { ServiceEditorState } from '../../editor-state/element-editor-state/service/ServiceEditorState.js';
import { LegendStudioUserDataHelper } from '../../../../__lib__/LegendStudioUserDataHelper.js';
export const getTestableMetadata = (testable, editorStore, extraTestableMetadataGetters) => {
if (testable instanceof PackageableElement) {
return {
testable,
id: getNullableIDFromTestable(testable, editorStore.graphManagerState.graph, editorStore.graphManagerState.pluginManager.getPureGraphManagerPlugins()) ?? uuid(),
name: testable.name,
};
}
const extraTestables = extraTestableMetadataGetters
.map((getter) => getter(testable, editorStore))
.filter(isNonNullable);
return (extraTestables[0] ?? {
testable,
id: uuid(),
name: '(unknown)',
});
};
// TreeData
export class TestableExplorerTreeNodeData {
isSelected;
isOpen;
id;
label;
childrenIds;
constructor(id, label) {
this.id = id;
this.label = label;
}
}
export class TestableTreeNodeData extends TestableExplorerTreeNodeData {
testableMetadata;
isRunning = false;
constructor(testable) {
super(testable.id, testable.id);
this.testableMetadata = testable;
makeObservable(this, {
isRunning: observable,
});
}
}
export class TestTreeNodeData extends TestableExplorerTreeNodeData {
isRunning = false;
constructor(id, label) {
super(id, label);
makeObservable(this, {
isRunning: observable,
});
}
}
export class AtomicTestTreeNodeData extends TestTreeNodeData {
atomicTest;
constructor(id, atomicTest) {
super(id, atomicTest.id);
this.atomicTest = atomicTest;
}
}
export class TestSuiteTreeNodeData extends TestTreeNodeData {
testSuite;
constructor(id, testSuite) {
super(id, testSuite.id);
this.testSuite = testSuite;
}
}
export class AssertionTestTreeNodeData extends TestableExplorerTreeNodeData {
assertion;
constructor(id, assertion) {
super(id, assertion.id);
this.assertion = assertion;
}
}
const buildTestNodeData = (test, parentId) => {
if (test instanceof AtomicTest) {
return new AtomicTestTreeNodeData(`${parentId}.${test.id}`, test);
}
else if (test instanceof TestSuite) {
return new TestSuiteTreeNodeData(`${parentId}.${test.id}`, test);
}
return undefined;
};
const buildChildrenIfPossible = (node, treeData) => {
if (!node.childrenIds) {
let children = [];
if (node instanceof TestableTreeNodeData) {
children = node.testableMetadata.testable.tests
.map((t) => buildTestNodeData(t, node.id))
.filter(isNonNullable);
}
else if (node instanceof TestSuiteTreeNodeData) {
children = node.testSuite.tests
.map((t) => buildTestNodeData(t, node.id))
.filter(isNonNullable);
}
else if (node instanceof AtomicTestTreeNodeData) {
children = node.atomicTest.assertions.map((assertion) => {
const assertionNode = new AssertionTestTreeNodeData(`${node.id}.${assertion.id}`, assertion);
return assertionNode;
});
}
node.childrenIds = children.map((c) => c.id);
children.forEach((c) => treeData.nodes.set(c.id, c));
}
};
const onTreeNodeSelect = (node, treeData) => {
buildChildrenIfPossible(node, treeData);
node.isOpen = !node.isOpen;
};
// Result Helpers
export const getAtomicTest_TestResult = (atomicTest, results) => results.get(atomicTest);
const getAssertion_TestResult = (assertion, results) => {
const test = assertion.parentTest;
return test ? getAtomicTest_TestResult(test, results) : undefined;
};
export const getAssertionStatus = (assertion, results) => {
const result = getAssertion_TestResult(assertion, results);
if (result instanceof TestExecuted &&
result.testExecutionStatus === TestExecutionStatus.FAIL) {
return result.assertStatuses.find((s) => s.assertion === assertion);
}
else if (result instanceof MultiExecutionServiceTestResult) {
const testAssertionStatus = new Map();
Array.from(result.keyIndexedTestResults.entries()).forEach(([key, testResult]) => {
if (testResult instanceof TestExecuted) {
const testAssertion = testResult.assertStatuses.find((s) => s.assertion === assertion);
if (testAssertion) {
testAssertionStatus.set(key, testAssertion);
}
}
});
return testAssertionStatus;
}
return undefined;
};
const getTestSuite_TestResults = (suite, results) => suite.tests.map((t) => getAtomicTest_TestResult(t, results));
const getTest_TestResults = (test, results) => {
if (test instanceof AtomicTest) {
return [getAtomicTest_TestResult(test, results)];
}
else if (test instanceof TestSuite) {
return getTestSuite_TestResults(test, results);
}
return [undefined];
};
const getTestable_TestResult = (test, results) => test.tests.flatMap((t) => getTest_TestResults(t, results));
export var TESTABLE_RESULT;
(function (TESTABLE_RESULT) {
TESTABLE_RESULT["DID_NOT_RUN"] = "DID_NOT_RUN";
TESTABLE_RESULT["ERROR"] = "ERROR";
TESTABLE_RESULT["FAILED"] = "FAILED";
TESTABLE_RESULT["PASSED"] = "PASSED";
TESTABLE_RESULT["IN_PROGRESS"] = "IN_PROGRESS";
TESTABLE_RESULT["NO_TESTS"] = "NO_TESTS";
})(TESTABLE_RESULT || (TESTABLE_RESULT = {}));
export const getTestableResultFromTestResult = (testResult) => {
if (testResult instanceof TestExecuted &&
testResult.testExecutionStatus === TestExecutionStatus.PASS) {
return TESTABLE_RESULT.PASSED;
}
else if (testResult instanceof TestExecuted &&
testResult.testExecutionStatus === TestExecutionStatus.FAIL) {
return TESTABLE_RESULT.FAILED;
}
else if (testResult instanceof TestError) {
return TESTABLE_RESULT.ERROR;
}
else if (testResult instanceof MultiExecutionServiceTestResult) {
const result = Array.from(testResult.keyIndexedTestResults.values());
if (result.every((t) => t instanceof TestExecuted &&
t.testExecutionStatus === TestExecutionStatus.PASS)) {
return TESTABLE_RESULT.PASSED;
}
else if (result.some((t) => t instanceof TestError)) {
return TESTABLE_RESULT.ERROR;
}
return TESTABLE_RESULT.FAILED;
}
return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getTestableResultFromAssertionStatus = (assertionStatus) => {
if (assertionStatus instanceof AssertPass) {
return TESTABLE_RESULT.PASSED;
}
else if (assertionStatus instanceof AssertFail) {
return TESTABLE_RESULT.FAILED;
}
else if (assertionStatus && !(assertionStatus instanceof AssertionStatus)) {
const assertionStatuses = Array.from(assertionStatus.values());
if (assertionStatuses.every((t) => t instanceof AssertPass)) {
return TESTABLE_RESULT.PASSED;
}
else {
return TESTABLE_RESULT.FAILED;
}
}
return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getTestableResultFromTestResults = (testResults) => {
if (!testResults?.length) {
return TESTABLE_RESULT.DID_NOT_RUN;
}
if (testResults.every((t) => t instanceof TestExecuted &&
t.testExecutionStatus === TestExecutionStatus.PASS)) {
return TESTABLE_RESULT.PASSED;
}
else if (testResults.find((t) => t instanceof TestError)) {
return TESTABLE_RESULT.ERROR;
}
else if (testResults.find((t) => t instanceof TestExecuted &&
t.testExecutionStatus === TestExecutionStatus.FAIL)) {
return TESTABLE_RESULT.FAILED;
}
else if (testResults.find((t) => t instanceof MultiExecutionServiceTestResult)) {
let result = [];
testResults.forEach((testResult) => {
if (testResult instanceof MultiExecutionServiceTestResult) {
result = result.concat(Array.from(testResult.keyIndexedTestResults.values()));
}
});
if (result.every((t) => t instanceof TestExecuted &&
t.testExecutionStatus === TestExecutionStatus.PASS)) {
return TESTABLE_RESULT.PASSED;
}
else if (result.some((t) => t instanceof TestError)) {
return TESTABLE_RESULT.ERROR;
}
return TESTABLE_RESULT.FAILED;
}
return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getNodeTestableResult = (node, globalRun, results) => {
if (globalRun && node instanceof TestableTreeNodeData) {
return TESTABLE_RESULT.IN_PROGRESS;
}
if ((node instanceof TestTreeNodeData ||
node instanceof TestableTreeNodeData) &&
node.isRunning) {
return TESTABLE_RESULT.IN_PROGRESS;
}
if (node instanceof AssertionTestTreeNodeData) {
const status = getAssertionStatus(node.assertion, results);
if (status) {
return getTestableResultFromAssertionStatus(status);
}
const result = node.assertion.parentTest
? results.get(node.assertion.parentTest)
: undefined;
return getTestableResultFromTestResult(result);
}
else if (node instanceof AtomicTestTreeNodeData) {
return getTestableResultFromTestResult(getAtomicTest_TestResult(node.atomicTest, results));
}
else if (node instanceof TestSuiteTreeNodeData) {
return getTestableResultFromTestResults(getTestSuite_TestResults(node.testSuite, results));
}
else if (node instanceof TestableTreeNodeData) {
return getTestableResultFromTestResults(getTestable_TestResult(node.testableMetadata.testable, results));
}
return TESTABLE_RESULT.DID_NOT_RUN;
};
export class TestableState {
uuid = uuid();
globalTestRunnerState;
editorStore;
testableMetadata;
treeData;
results = new Map();
isRunningTests = ActionState.create();
constructor(editorStore, globalTestRunnerState, testable) {
makeObservable(this, {
editorStore: false,
testableMetadata: observable,
isRunningTests: observable,
results: observable,
treeData: observable.ref,
handleTestableResult: action,
setTreeData: action,
onTreeNodeSelect: action,
run: flow,
});
this.editorStore = editorStore;
this.globalTestRunnerState = globalTestRunnerState;
this.testableMetadata = getTestableMetadata(testable, editorStore, this.globalTestRunnerState.extraTestableMetadataGetters);
this.treeData = this.buildTreeData(this.testableMetadata);
}
*run(node) {
this.isRunningTests.inProgress();
let input;
let currentNode = node;
try {
if (node instanceof AssertionTestTreeNodeData) {
const atomicTest = guaranteeNonNullable(node.assertion.parentTest);
const suite = atomicTest.__parent instanceof TestSuite
? atomicTest.__parent
: undefined;
input = new RunTestsTestableInput(this.testableMetadata.testable);
input.unitTestIds = [new UniqueTestId(suite, atomicTest)];
const parentNode = Array.from(this.treeData.nodes.values())
.filter(filterByType(AtomicTestTreeNodeData))
.find((n) => n.atomicTest === atomicTest);
if (parentNode) {
currentNode = parentNode;
parentNode.isRunning = true;
}
}
else if (node instanceof AtomicTestTreeNodeData) {
const atomicTest = node.atomicTest;
const suite = atomicTest.__parent instanceof TestSuite
? atomicTest.__parent
: undefined;
input = new RunTestsTestableInput(this.testableMetadata.testable);
input.unitTestIds = [new UniqueTestId(suite, atomicTest)];
node.isRunning = true;
}
else if (node instanceof TestSuiteTreeNodeData) {
input = new RunTestsTestableInput(this.testableMetadata.testable);
input.unitTestIds = node.testSuite.tests.map((s) => new UniqueTestId(node.testSuite, s));
node.isRunning = true;
}
else if (node instanceof TestableTreeNodeData) {
input = new RunTestsTestableInput(this.testableMetadata.testable);
node.isRunning = true;
}
else {
throw new UnsupportedOperationError(`Unable to run tests for node ${node}`);
}
const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests([input], this.editorStore.graphManagerState.graph));
this.globalTestRunnerState.handleResults(testResults);
this.isRunningTests.complete();
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notificationService.notifyError(error);
this.isRunningTests.fail();
}
finally {
if (currentNode instanceof TestTreeNodeData ||
currentNode instanceof TestableTreeNodeData) {
currentNode.isRunning = false;
}
}
}
handleTestableResult(testResult, openAssertions) {
try {
assertTrue(testResult.testable === this.testableMetadata.testable);
this.results.set(testResult.atomicTest, testResult);
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notificationService.notifyError(`Unable to update test result: ${error.message}`);
}
}
buildTreeData(testable) {
const rootIds = [];
const nodes = new Map();
const treeData = { rootIds, nodes };
const testableTreeNodeData = new TestableTreeNodeData(testable);
treeData.rootIds.push(testableTreeNodeData.id);
treeData.nodes.set(testableTreeNodeData.id, testableTreeNodeData);
return treeData;
}
setTreeData(data) {
this.treeData = data;
}
onTreeNodeSelect(node, treeData) {
onTreeNodeSelect(node, treeData);
this.setTreeData({ ...treeData });
}
}
export class GlobalTestRunnerState {
editorStore;
sdlcState;
extraTestableMetadataGetters = [];
// current project
isRunningTests = ActionState.create();
testableStates;
// dependencies
showDependencyPanel = false;
isRunningDependencyTests = ActionState.create();
dependencyTestableStates;
// error
failureViewing;
constructor(editorStore, sdlcState) {
makeObservable(this, {
editorStore: false,
sdlcState: false,
testableStates: observable,
dependencyTestableStates: observable,
isRunningTests: observable,
isRunningDependencyTests: observable,
initOwnTestables: action,
runAllTests: flow,
runDependenciesTests: flow,
failureViewing: observable,
showDependencyPanel: observable,
setFailureViewing: action,
setShowDependencyPanel: action,
initDependency: action,
visitTestable: action,
});
this.editorStore = editorStore;
this.sdlcState = sdlcState;
this.extraTestableMetadataGetters = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraTestableMetadata?.() ?? [])
.filter(isNonNullable);
const showDependencyPanelVal = LegendStudioUserDataHelper.globalTestRunner_getShowDependencyPanel(this.editorStore.applicationStore.userDataService);
if (showDependencyPanelVal !== undefined) {
this.showDependencyPanel = showDependencyPanelVal;
}
}
get ownTestableStates() {
return this.testableStates ?? [];
}
get allDependencyTestablesStates() {
return this.dependencyTestableStates ?? [];
}
get allTestableStates() {
return [...this.ownTestableStates, ...this.allDependencyTestablesStates];
}
get isDispatchingOwnProjectAction() {
return (this.isRunningTests.isInProgress ||
this.ownTestableStates.some((s) => s.isRunningTests.isInProgress));
}
get isDispatchingDependencyAction() {
return (this.isRunningDependencyTests.isInProgress ||
this.allDependencyTestablesStates.some((s) => s.isRunningTests.isInProgress));
}
initOwnTestables(force) {
if (!this.testableStates || force) {
const testables = this.editorStore.graphManagerState.graph.ownTestables;
this.testableStates = testables.map((testable) => new TestableState(this.editorStore, this, testable));
}
}
visitTestable(testable) {
if (testable instanceof PackageableElement) {
this.editorStore.graphEditorMode.openElement(testable);
const currentTab = this.editorStore.tabManagerState.currentTab;
// TODO: should be abstracted onto a `TestableEditorState`
if (currentTab instanceof ServiceEditorState) {
currentTab.openToTestTab();
}
}
}
setFailureViewing(val) {
this.failureViewing = val;
}
*runAllTests() {
try {
this.isRunningTests.inProgress();
const inputs = this.ownTestableStates.map((e) => new RunTestsTestableInput(e.testableMetadata.testable));
const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests(inputs, this.editorStore.graphManagerState.graph));
this.handleResults(testResults);
this.isRunningTests.complete();
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notificationService.notifyError(error);
this.isRunningTests.fail();
}
}
handleResults(testResults) {
testResults.forEach((testResult) => {
const testableState = this.allTestableStates.find((tState) => tState.testableMetadata.testable === testResult.testable);
if (testableState) {
testableState.handleTestableResult(testResult, true);
}
});
}
// dependency
setShowDependencyPanel(val) {
this.showDependencyPanel = val;
if (this.showDependencyPanel) {
this.initDependency();
}
LegendStudioUserDataHelper.globalTestRunner_setShowDependencyPanel(this.editorStore.applicationStore.userDataService, val);
}
initDependency() {
if (!this.dependencyTestableStates) {
this.dependencyTestableStates =
this.editorStore.graphManagerState.graph.dependencyManager.testables.map((testable) => new TestableState(this.editorStore, this, testable));
}
}
*runDependenciesTests() {
try {
this.isRunningDependencyTests.inProgress();
const inputs = this.allDependencyTestablesStates.map((e) => new RunTestsTestableInput(e.testableMetadata.testable));
const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests(inputs, this.editorStore.graphManagerState.graph));
this.handleResults(testResults);
this.isRunningDependencyTests.complete();
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notificationService.notifyError(error);
this.isRunningDependencyTests.fail();
}
}
}
//# sourceMappingURL=GlobalTestRunnerState.js.map