UNPKG

@itwin/core-backend

Version:
369 lines • 19.8 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { assert } from "chai"; import { DbResult } from "@itwin/core-bentley"; import { SnapshotDb } from "../../../core-backend"; import { KnownTestLocations } from "../../KnownTestLocations"; import { ECSqlValueType, QueryBinder, QueryRowFormat } from "@itwin/core-common"; import { buildBinaryData, ECDbMarkdownTestParser, ECDbTestMode, ECDbTestRowFormat } from "./ECSqlTestParser"; import * as path from "path"; import * as fs from "fs"; import { ECSqlDatasets } from "../dataset/ECSqlDatasets"; import { Point2d, Point3d } from "@itwin/core-geometry"; var TestDataset; (function (TestDataset) { TestDataset["AllProperties"] = "AllProperties.bim"; })(TestDataset || (TestDataset = {})); const snapshotDbs = {}; describe("Markdown based ECDb test runner", async () => { before(async () => { await ECSqlDatasets.generateFiles(); const datasetFilePath = path.join(KnownTestLocations.outputDir, "ECSqlTests", TestDataset.AllProperties); if (!fs.existsSync(datasetFilePath)) { throw new Error(`Dataset file ${datasetFilePath} does not exist`); } snapshotDbs[TestDataset.AllProperties] = SnapshotDb.openFile(datasetFilePath); }); after(() => { for (const key in snapshotDbs) { if (snapshotDbs.hasOwnProperty(key)) { (snapshotDbs[key])?.close(); } } }); const tests = ECDbMarkdownTestParser.parse(); //TODO: Mechanism to run a single test, put something like it.only into the test md which causes this loop to only run those tests for (const test of tests) { if (!test.dataset) { logWarning(`Skipping test ${test.title} because it does not have a dataset`); continue; } if (test.dataset.toLowerCase() !== TestDataset.AllProperties.toLowerCase()) { logWarning(`Skipping test ${test.title} because dataset ${test.dataset} is not recognized`); continue; } const dataset = TestDataset.AllProperties; if (test.mode === ECDbTestMode.Both || test.mode === ECDbTestMode.Statement) { if (test.skip) it(`${test.fileName}: ${test.title} (Statement) skipped. Reason: ${test.skip}`); else if (test.only) it.only(`${test.fileName}: ${test.title} (Statement)`, () => { runECSqlStatementTest(test, dataset); }); else it(`${test.fileName}: ${test.title} (Statement)`, () => { runECSqlStatementTest(test, dataset); }); } if (test.mode === ECDbTestMode.Both || test.mode === ECDbTestMode.ConcurrentQuery) { if (test.skip) it(`${test.fileName}: ${test.title} (ConcurrentQuery) skipped. Reason: ${test.skip}`); else if (test.only) it.only(`${test.fileName}: ${test.title} (ConcurrentQuery)`, async () => { await runConcurrentQueryTest(test, dataset); }); else it(`${test.fileName}: ${test.title} (ConcurrentQuery)`, async () => { await runConcurrentQueryTest(test, dataset); }); } } }); function runECSqlStatementTest(test, dataset) { const imodel = snapshotDbs[dataset]; if (!imodel) { assert.fail(`Dataset ${dataset} is not loaded`); } // eslint-disable-next-line @typescript-eslint/no-deprecated let stmt; if (test.sql === undefined) { assert.fail("Test does not have an ECSql statement"); } try { // TODO: statement options should be exposed through the markdown // eslint-disable-next-line @typescript-eslint/no-deprecated stmt = imodel.prepareStatement(test.sql); // TODO: Wire up logic for tests we expect to fail during prepare } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during prepare of Statement: ${error.message}`); } if (test.errorDuringPrepare) assert.fail(`Statement is expected to fail during prepare`); try { if (test.binders !== undefined) { for (const binder of test.binders) { // eslint-disable-next-line radix let id = Number.parseInt(binder.indexOrName); if (isNaN(id)) id = binder.indexOrName; switch (binder.type.toLowerCase()) { // TODO: replace props variables in binder.value case "null": stmt.bindNull(id); break; case "string": stmt.bindString(id, binder.value); break; case "int": // eslint-disable-next-line radix stmt.bindInteger(id, Number.parseInt(binder.value)); break; case "double": stmt.bindDouble(id, Number.parseFloat(binder.value)); break; case "id": stmt.bindId(id, binder.value); break; case "idset": const values = binder.value.slice(1, -1).split(","); const trimmedValues = values.map((value) => value.trim()); stmt.bindIdSet(id, trimmedValues); break; case "datetime": stmt.bindDateTime(id, binder.value); break; case "point2d": const parsedVal2d = JSON.parse(binder.value); stmt.bindPoint2d(id, { x: parsedVal2d.X, y: parsedVal2d.Y }); break; case "point3d": const parsedVal3d = JSON.parse(binder.value); stmt.bindPoint3d(id, { x: parsedVal3d.X, y: parsedVal3d.Y, z: parsedVal3d.Z }); break; case "blob": const arrayValues = binder.value.slice(1, -1).split(","); const numbers = arrayValues.map((value) => // eslint-disable-next-line radix parseInt(value.trim())); stmt.bindBlob(id, Uint8Array.of(...numbers)); break; case "navigation": stmt.bindNavigation(id, JSON.parse(binder.value)); break; case "array": stmt.bindArray(id, JSON.parse(binder.value)); break; case "struct": stmt.bindStruct(id, JSON.parse(binder.value)); break; default: assert.fail(`Unsupported binder type ${binder.type}`); } // switch binder.type } // for binder } // if test.binders let resultCount = 0; let stepResult; while ((stepResult = stmt.step()) === DbResult.BE_SQLITE_ROW) { if (resultCount === 0 && test.columnInfo) { // Verify the columns on the first result row (TODO: for dynamic columns we have to do this every item) const colCount = stmt.getColumnCount(); assert.strictEqual(colCount, test.columnInfo.length, `Expected ${test.columnInfo.length} columns but got ${colCount}`); for (let i = 0; i < colCount; i++) { const colInfo = stmt.getValue(i).columnInfo; const expectedColInfo = test.columnInfo[i]; // cannot directly compare against colInfo because it has methods instead of getters assert.strictEqual(colInfo.getPropertyName(), expectedColInfo.name, `Expected property name ${expectedColInfo.name} but got ${colInfo.getPropertyName()} for column index ${i}`); //if (expectedColInfo.isDynamicProp !== undefined) TODO: Is this not exposed? // assert.strictEqual(colInfo..isDynamicProperty, expectedColInfo.isDynamicProp, `Expected dynamic property ${expectedColInfo.isDynamicProp} but got ${colInfo.isDynamicProperty} for column index ${i}`); // TODO: Extended type name not exposed?? if (expectedColInfo.generated !== undefined) assert.strictEqual(colInfo.isGeneratedProperty(), expectedColInfo.generated, `Expected generated property ${expectedColInfo.generated} but got ${colInfo.isGeneratedProperty()} for column index ${i}`); if (expectedColInfo.accessString !== undefined) assert.strictEqual(colInfo.getAccessString(), expectedColInfo.accessString, `Expected access string ${expectedColInfo.accessString} but got ${colInfo.getAccessString()} for column index ${i}`); if (expectedColInfo.type !== undefined) assert.strictEqual(ECSqlValueType[colInfo.getType()], expectedColInfo.type, `Expected type ${expectedColInfo.type} but got ${ECSqlValueType[colInfo.getType()]} for column index ${i}`); if (expectedColInfo.originPropertyName !== undefined) assert.strictEqual(colInfo.getOriginPropertyName(), expectedColInfo.originPropertyName, `Expected Origin PropertyName ${expectedColInfo.originPropertyName} but got ${colInfo.getOriginPropertyName()} for column index ${i}`); } } if (test.expectedResults !== undefined && test.expectedResults.length > resultCount) { let expectedResult = test.expectedResults[resultCount]; expectedResult = buildBinaryData(expectedResult); const rowArgs = { rowFormat: getRowFormat(test.rowFormat), classIdsToClassNames: test.convertClassIdsToClassNames }; const actualResult = stmt.getRow(rowArgs); checkingExpectedResults(test.rowFormat, actualResult, expectedResult, test.indexesToIncludeInResults); } resultCount++; } stmt[Symbol.dispose](); stmt = undefined; if (resultCount === 0 && test.stepStatus) { const stepResultString = DbResult[stepResult]; assert.strictEqual(stepResultString, test.stepStatus, `Expected step status ${test.stepStatus} but got ${stepResultString}`); } if (test.expectedResults && test.expectedResults.length !== resultCount) { assert.fail(`Expected ${test.expectedResults.length} rows but got ${resultCount}`); } } finally { if (stmt !== undefined) stmt[Symbol.dispose](); } } function getRowFormat(rowFormat) { switch (rowFormat) { case ECDbTestRowFormat.ECSqlNames: return QueryRowFormat.UseECSqlPropertyNames; case ECDbTestRowFormat.ECSqlIndexes: return QueryRowFormat.UseECSqlPropertyIndexes; case ECDbTestRowFormat.JsNames: return QueryRowFormat.UseJsPropertyNames; default: return QueryRowFormat.UseECSqlPropertyNames; } } async function runConcurrentQueryTest(test, dataset) { const imodel = snapshotDbs[dataset]; if (!imodel) { assert.fail(`Dataset ${dataset} is not loaded`); } let reader; if (test.sql === undefined) { assert.fail("Test does not have an ECSql statement"); } let params; if (test.binders !== undefined) { params = new QueryBinder(); for (const binder of test.binders) { // eslint-disable-next-line radix let id = Number.parseInt(binder.indexOrName); if (isNaN(id)) id = binder.indexOrName; switch (binder.type.toLowerCase()) { // TODO: replace props variables in binder.value case "null": params.bindNull(id); break; case "string": params.bindString(id, binder.value); break; case "int": // eslint-disable-next-line radix params.bindInt(id, Number.parseInt(binder.value)); break; case "long": // eslint-disable-next-line radix params.bindLong(id, Number.parseInt(binder.value)); break; case "double": params.bindDouble(id, Number.parseFloat(binder.value)); break; case "id": params.bindId(id, binder.value); break; case "idset": const values = binder.value.slice(1, -1).split(","); const trimmedValues = values.map((value) => value.trim()); params.bindIdSet(id, trimmedValues); break; case "point2d": const parsedVal2d = JSON.parse(binder.value); params.bindPoint2d(id, new Point2d(parsedVal2d.X, parsedVal2d.Y)); break; case "point3d": const parsedVal3d = JSON.parse(binder.value); params.bindPoint3d(id, new Point3d(parsedVal3d.X, parsedVal3d.Y, parsedVal3d.Z)); break; case "blob": const arrayValues = binder.value.slice(1, -1).split(","); const numbers = arrayValues.map((value) => // eslint-disable-next-line radix parseInt(value.trim())); params.bindBlob(id, Uint8Array.of(...numbers)); break; case "struct": params.bindStruct(id, JSON.parse(binder.value)); break; default: assert.fail(`Unsupported binder type ${binder.type}`); } // switch binder.type } // for binder } // if test.binders const queryOptions = {}; queryOptions.rowFormat = getRowFormat(test.rowFormat); if (test.abbreviateBlobs) queryOptions.abbreviateBlobs = true; if (test.convertClassIdsToClassNames) { // eslint-disable-next-line @typescript-eslint/no-deprecated queryOptions.convertClassIdsToClassNames = true; } try { reader = imodel.createQueryReader(test.sql, params, queryOptions); // TODO: Wire up logic for tests we expect to fail during prepare } catch (error) { assert.fail(`Error during creating QueryReader: ${error.message}`); } let resultCount = 0; let rows; try { rows = await reader.toArray(); } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during prepare of Concurrent Query: ${error.message}`); } if (test.errorDuringPrepare) assert.fail(`Statement is expected to fail during prepare`); const colMetaData = await reader.getMetaData(); while (resultCount < rows.length) { if (resultCount === 0 && test.columnInfo) { // Verify the columns on the first result row (TODO: for dynamic columns we have to do this every item) assert.strictEqual(colMetaData.length, test.columnInfo.length, `Expected ${test.columnInfo.length} columns but got ${colMetaData.length}`); for (let i = 0; i < colMetaData.length; i++) { const colInfo = colMetaData[i]; const expectedColInfo = test.columnInfo[i]; // cannot directly compare against colInfo because it has methods instead of getters assert.strictEqual(colInfo.name, expectedColInfo.name, `Expected name ${expectedColInfo.name} but got ${colInfo.name} for column index ${i}`); if (expectedColInfo.generated !== undefined) assert.strictEqual(colInfo.generated, expectedColInfo.generated, `Expected generated property ${expectedColInfo.generated} but got ${colInfo.generated} for column index ${i}`); if (expectedColInfo.accessString !== undefined) assert.strictEqual(colInfo.accessString, expectedColInfo.accessString, `Expected access string ${expectedColInfo.accessString} but got ${colInfo.accessString} for column index ${i}`); if (expectedColInfo.typeName !== undefined) assert.strictEqual(colInfo.typeName, expectedColInfo.typeName, `Expected type name ${expectedColInfo.typeName} but got ${colInfo.typeName} for column index ${i}`); if (expectedColInfo.className !== undefined) assert.strictEqual(colInfo.className, expectedColInfo.className, `Expected class name ${expectedColInfo.className} but got ${colInfo.className} for column index ${i}`); assert.strictEqual(colInfo.extendedType, expectedColInfo.extendedType, `Expected extended type ${expectedColInfo.extendedType} but got ${colInfo.extendedType} for column index ${i}`); assert.strictEqual(colInfo.extendType, expectedColInfo.extendedType === undefined ? "" : expectedColInfo.extendedType, `Expected extend type ${expectedColInfo.extendedType === undefined ? "" : expectedColInfo.extendedType} but got ${colInfo.extendType} for column index ${i}`); // eslint-disable-line @typescript-eslint/no-deprecated } } if (test.expectedResults !== undefined && test.expectedResults.length > resultCount) { let expectedResult = test.expectedResults[resultCount]; // replace props in expected result, TODO: optimize this expectedResult = buildBinaryData(expectedResult); const actualResult = rows[resultCount]; // TODO: should we test getValue() as well? checkingExpectedResults(test.rowFormat, actualResult, expectedResult, test.indexesToIncludeInResults); } resultCount++; } // if (resultCount === 0 && test.stepStatus) { // const stepResultString = DbResult[stepResult]; // assert.strictEqual(stepResultString, test.stepStatus, `Expected step status ${test.stepStatus} but got ${stepResultString}`); // } if (test.expectedResults && test.expectedResults.length !== resultCount) { assert.fail(`Expected ${test.expectedResults.length} rows but got ${resultCount}`); } } function checkingExpectedResults(rowFormat, actualResult, expectedResult, indexesToInclude) { if (rowFormat === ECDbTestRowFormat.ECSqlIndexes && indexesToInclude) { let i = 0; for (const key of Object.keys(expectedResult)) { assert.deepEqual(actualResult[indexesToInclude[i]], expectedResult[key], `Expected ${JSON.stringify(expectedResult[key])} but got ${JSON.stringify(actualResult[indexesToInclude[i]])}`); i++; } } else { for (const key of Object.keys(expectedResult)) { assert.deepEqual(actualResult[key], expectedResult[key], `Expected ${JSON.stringify(expectedResult[key])} but got ${JSON.stringify(actualResult[key])}`); } } } function logWarning(message) { // eslint-disable-next-line no-console console.log(`\x1b[33m${message}\x1b[0m`); } //# sourceMappingURL=ECSqlTestRunner.test.js.map