UNPKG

@finos/legend-application-studio

Version:
517 lines 21.2 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 { 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