@finos/legend-studio
Version:
413 lines • 16.3 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 { RunTestsTestableInput, TestSuite, AtomicTest, AtomicTestId, TestError, TestFailed, TestPassed, 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 { getElementTypeIcon } from '../../../components/shared/ElementIconUtils.js';
export const getTestableMetadata = (testable, editorStore, extraTestableMetadataGetters) => {
if (testable instanceof PackageableElement) {
return {
testable: testable,
id: getNullableIDFromTestable(testable, editorStore.graphManagerState.graph, editorStore.graphManagerState.pluginManager.getPureGraphManagerPlugins()) ?? uuid(),
name: testable.name,
icon: getElementTypeIcon(editorStore, editorStore.graphState.getPackageableElementType(testable)),
};
}
const extraTestables = extraTestableMetadataGetters
.map((getter) => getter(testable, editorStore))
.filter(isNonNullable);
return (extraTestables[0] ?? {
testable,
id: uuid(),
name: '(unknown)',
icon: null,
});
};
// 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 TestFailed) {
return result.assertStatuses.find((s) => s.assertion === assertion);
}
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 = TESTABLE_RESULT || (TESTABLE_RESULT = {}));
export const getTestableResultFromTestResult = (testResult) => {
if (testResult instanceof TestPassed) {
return TESTABLE_RESULT.PASSED;
}
else if (testResult instanceof TestFailed) {
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 TestPassed)) {
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;
}
return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getTestableResultFromTestResults = (testResults) => {
if (testResults.every((t) => t instanceof TestPassed)) {
return TESTABLE_RESULT.PASSED;
}
else if (testResults.find((t) => t instanceof TestError)) {
return TESTABLE_RESULT.ERROR;
}
else if (testResults.find((t) => t instanceof TestFailed)) {
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 AtomicTestId(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 AtomicTestId(suite, atomicTest)];
node.isRunning = true;
}
else if (node instanceof TestSuiteTreeNodeData) {
input = new RunTestsTestableInput(this.testableMetadata.testable);
input.unitTestIds = node.testSuite.tests.map((s) => new AtomicTestId(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.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.atomicTestId.atomicTest, testResult);
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.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;
testableStates;
isRunningTests = ActionState.create();
extraTestableMetadataGetters = [];
failureViewing;
constructor(editorStore, sdlcState) {
makeObservable(this, {
editorStore: false,
sdlcState: false,
testableStates: observable,
init: action,
runAllTests: flow,
failureViewing: observable,
setFailureViewing: action,
});
this.editorStore = editorStore;
this.sdlcState = sdlcState;
this.extraTestableMetadataGetters = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraTestableMetadata?.() ?? [])
.filter(isNonNullable);
}
init(force) {
if (!this.testableStates || force) {
const testables = this.editorStore.graphManagerState.graph.allOwnTestables;
this.testableStates = testables.map((testable) => new TestableState(this.editorStore, this, testable));
}
}
get testables() {
return this.testableStates ?? [];
}
get isDispatchingAction() {
return (this.isRunningTests.isInProgress ||
this.testables.some((s) => s.isRunningTests.isInProgress));
}
setFailureViewing(val) {
this.failureViewing = val;
}
*runAllTests(testableState) {
try {
this.isRunningTests.inProgress();
let inputs = [];
if (!testableState) {
inputs = (this.testableStates ?? []).map((e) => new RunTestsTestableInput(e.testableMetadata.testable));
}
else {
inputs = [
new RunTestsTestableInput(testableState.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.notifyError(error);
this.isRunningTests.fail();
}
}
handleResults(testResults) {
testResults.forEach((testResult) => {
const testableState = this.testables.find((tState) => tState.testableMetadata.testable === testResult.testable);
if (testableState) {
testableState.handleTestableResult(testResult, true);
}
});
}
}
//# sourceMappingURL=GlobalTestRunnerState.js.map