UNPKG

@finos/legend-studio

Version:
413 lines 16.3 kB
/** * 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