UNPKG

@finos/legend-application-pure-ide

Version:
346 lines 13.4 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 { action, computed, flow, flowResult, makeObservable, observable, } from 'mobx'; import { ActionState, addUniqueEntry, assertErrorThrown, assertTrue, guaranteeNonNullable, guaranteeType, promisify, UnsupportedOperationError, } from '@finos/legend-shared'; import { deserializeTestRunnerCheckResult, PCTAdapter, TestFailureResult, TestResultStatus, TestRunnerCheckResult, } from '../server/models/Test.js'; const getFullParentId = (testInfo, testExecutionResult) => `test${testExecutionResult.runnerId}_${testInfo.li_attr.parentId}`; const getFullTestId = (testResult, testExecutionResult) => `test${testExecutionResult.runnerId}_${testResult.test.join('_')}`; export var TestSuiteStatus; (function (TestSuiteStatus) { TestSuiteStatus["PASSED"] = "PASSED"; TestSuiteStatus["FAILED"] = "FAILED"; TestSuiteStatus["NONE"] = "NONE"; })(TestSuiteStatus || (TestSuiteStatus = {})); export var TestResultType; (function (TestResultType) { TestResultType["PASSED"] = "PASSED"; TestResultType["FAILED"] = "FAILED"; TestResultType["ERROR"] = "ERROR"; TestResultType["RUNNING"] = "RUNNING"; })(TestResultType || (TestResultType = {})); export const getTestResultById = (id, testResultInfo) => testResultInfo.passedTests.has(id) ? TestResultType.PASSED : testResultInfo.failedTests.has(id) ? TestResultType.FAILED : testResultInfo.testsWithError.has(id) ? TestResultType.ERROR : TestResultType.RUNNING; export const getTestTreeNodeStatus = (node, testResultInfo) => { const id = node.id; const isLeafNode = Boolean(node.data.type); if (isLeafNode) { return getTestResultById(id, testResultInfo); } // order matters here, also if one test fail/error the whole sub-tree (package) will be marked as failed // NOTE: here, we have to check `startsWith(`${id}_`)` to guarantee we grab the right package, if we just use `startsWith(id)` // we might mark `meta::test` and `meta::test2` both as failed when `meta::test::someTest` fails return testResultInfo.failedTestIds.some((i) => i.startsWith(`${id}_`)) || testResultInfo.testWithErrorIds.some((i) => i.startsWith(`${id}_`)) ? TestResultType.FAILED : testResultInfo.notRunTestIds.some((i) => i.startsWith(`${id}_`)) ? TestResultType.RUNNING : TestResultType.PASSED; }; export class TestResultInfo { _START_TIME; total; time = 0; // ms passedTests = new Set(); failedTests = new Map(); testsWithError = new Map(); notRunTests; results = new Map(); constructor(allTestIds) { makeObservable(this, { total: observable, time: observable, passedTests: observable, failedTests: observable, testsWithError: observable, notRunTests: observable, results: observable, passed: computed, error: computed, failed: computed, passedTestIds: computed, failedTestIds: computed, testWithErrorIds: computed, notRunTestIds: computed, numberOfTestsRun: computed, runPercentage: computed, suiteStatus: computed, setTime: action, addResult: action, }); this._START_TIME = Date.now(); this.total = allTestIds.size; this.notRunTests = new Set(allTestIds); } get passed() { return this.passedTests.size; } get error() { return this.testsWithError.size; } get failed() { return this.failedTests.size; } get passedTestIds() { return Array.from(this.passedTests.values()); } get failedTestIds() { return Array.from(this.failedTests.keys()); } get testWithErrorIds() { return Array.from(this.testsWithError.keys()); } get notRunTestIds() { return Array.from(this.notRunTests.values()); } get numberOfTestsRun() { return this.passed + this.error + this.failed; } get runPercentage() { return Math.floor((this.numberOfTestsRun * 100) / this.total); } get suiteStatus() { return this.failed + this.error ? TestSuiteStatus.FAILED : this.passed ? TestSuiteStatus.PASSED : TestSuiteStatus.NONE; } setTime(val) { this.time = val; } addResult(result, testId) { this.results.set(testId, result); this.notRunTests.delete(testId); switch (result.status) { case TestResultStatus.PASSED: { this.passedTests.add(testId); break; } case TestResultStatus.FAILED: { this.failedTests.set(testId, guaranteeType(result, TestFailureResult)); break; } case TestResultStatus.ERROR: { this.testsWithError.set(testId, guaranteeType(result, TestFailureResult)); break; } default: { throw new UnsupportedOperationError(`Unsupported test result status '${result.status}'`); } } this.time = Date.now() - this._START_TIME; } } export class TestRunnerState { ideStore; testExecutionResult; checkTestRunnerState = ActionState.create(); testResultInfo; allTests = new Map(); selectedTestId; // explorer tree treeBuildingState = ActionState.create(); treeData; viewAsList = false; initState = ActionState.create(); adapters = []; constructor(ideStore, testExecutionResult) { makeObservable(this, { testResultInfo: observable.ref, allTests: observable, selectedTestId: observable, treeData: observable.ref, viewAsList: observable, setViewAsList: action, setSelectedTestId: action, setTestResultInfo: action, setTreeData: action, refreshTree: action, collapseTree: action, expandTree: action, buildTreeDataByLayer: action, pullTestRunnerResult: action, initialize: flow, buildTestTreeData: flow, pollTestRunnerResult: flow, rerunTestSuite: flow, cancelTestRun: flow, }); this.ideStore = ideStore; this.testExecutionResult = testExecutionResult; } getTreeData() { return guaranteeNonNullable(this.treeData, 'Test tree data has not been initialized'); } setViewAsList(val) { this.viewAsList = val; } setSelectedTestId(val) { this.selectedTestId = val; } setTestResultInfo(val) { this.testResultInfo = val; } setTreeData(data) { this.treeData = data; } refreshTree() { this.setTreeData({ ...guaranteeNonNullable(this.treeData) }); } *initialize() { if (this.initState.isInProgress) { this.ideStore.applicationStore.notificationService.notifyWarning('Test runner initialization is in progress'); return; } this.initState.inProgress(); try { this.adapters = (yield this.ideStore.client.getPCTAdapters()).map((adapter) => new PCTAdapter(adapter.first, adapter.second)); this.initState.pass(); } catch (error) { assertErrorThrown(error); this.ideStore.applicationStore.notificationService.notifyError(error); this.initState.fail(); } } *buildTestTreeData() { if (this.treeBuildingState.isInProgress) { return; } this.treeBuildingState.inProgress(); const rootIds = this.testExecutionResult.tests.map((test) => { const id = test.li_attr.id; if (test.type) { this.allTests.set(id, test); } return id; }); const nodes = new Map(); this.treeData = { rootIds, nodes }; yield this.buildTreeDataByLayer(this.testExecutionResult.tests); this.treeBuildingState.reset(); } collapseTree() { const treeData = this.getTreeData(); treeData.nodes.forEach((node) => { node.isOpen = false; }); this.refreshTree(); } expandTree() { const treeData = this.getTreeData(); treeData.nodes.forEach((node) => { node.isOpen = true; }); this.refreshTree(); } async buildTreeDataByLayer(tests) { const treeData = this.getTreeData(); const childLevelTests = []; await Promise.all(tests.map((test) => new Promise((resolve, reject) => setTimeout(() => { const id = test.li_attr.id; const node = { id: id, label: test.text, data: test, childrenIds: test.type ? undefined : [], isLoading: false, }; if (test.type) { this.allTests.set(id, test); } treeData.nodes.set(id, node); if (test.li_attr.parentId !== 'Root') { try { const parentNode = guaranteeNonNullable(treeData.nodes.get(getFullParentId(test, this.testExecutionResult)), `Can't find parent test node with ID '${test.li_attr.parentId}'`); if (parentNode.childrenIds) { addUniqueEntry(parentNode.childrenIds, id); } else { parentNode.childrenIds = [id]; } } catch (error) { reject(error); return; } } childLevelTests.push(...test.children); resolve(); }, 0)))); if (childLevelTests.length) { return this.buildTreeDataByLayer(childLevelTests); } return Promise.resolve(); } *pollTestRunnerResult() { if (!this.checkTestRunnerState.isInInitialState) { return; } this.checkTestRunnerState.inProgress(); try { assertTrue(this.allTests.size === this.testExecutionResult.count, `Number of tests scanned in tree (${this.allTests.size}) does not match the number of total reported tests (${this.testExecutionResult.count})`); const testResultInfo = new TestResultInfo(new Set(this.allTests.keys())); this.testResultInfo = testResultInfo; yield this.pullTestRunnerResult(testResultInfo); } finally { this.checkTestRunnerState.reset(); } } async pullTestRunnerResult(testResultInfo) { const result = deserializeTestRunnerCheckResult(await this.ideStore.client.checkTestRunner(this.testExecutionResult.runnerId)); if (result instanceof TestRunnerCheckResult) { await Promise.all(result.tests.map((test) => promisify(() => testResultInfo.addResult(test, getFullTestId(test, this.testExecutionResult))))); if (!result.finished) { return new Promise((resolve, reject) => setTimeout(() => { try { resolve(this.pullTestRunnerResult(testResultInfo)); } catch (error) { assertErrorThrown(error); this.ideStore.applicationStore.notificationService.notifyWarning(`Failed to run test${error.message ? `: ${error.message}` : ''}`); reject(error); } // NOTE: this call might take a while so we need to tune this depending on the performance of the app }, 1000)); } return Promise.resolve(); } // test runner check error -> runner has been cancelled this.setTestResultInfo(undefined); return Promise.resolve(); } *rerunTestSuite() { if (this.ideStore.testRunState.isInProgress) { return; } yield flowResult(this.ideStore.executeTests(this.testExecutionResult.path, this.testExecutionResult.relevantTestsOnly, this.testExecutionResult.pctAdapter)); } *cancelTestRun() { if (!this.ideStore.testRunState.isInProgress) { return; } yield this.ideStore.client.cancelTestRunner(this.testExecutionResult.runnerId); this.ideStore.applicationStore.notificationService.notifyWarning(`Stopped running test (id: ${this.testExecutionResult.runnerId}) successfully!`); } } //# sourceMappingURL=TestRunnerState.js.map