UNPKG

@itwin/core-backend

Version:
456 lines • 23.3 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.QueryReader) { if (test.skip) { it(`${test.fileName}: ${test.title} (ECSqlReader) skipped. Reason: ${test.skip}`); it(`${test.fileName}: ${test.title} (ECSqlSyncReader) skipped. Reason: ${test.skip}`); } else if (test.only) { it.only(`${test.fileName}: ${test.title} (ECSqlReader)`, async () => { await runECSqlReaderTest(test, dataset); }); it.only(`${test.fileName}: ${test.title} (ECSqlSyncReader)`, async () => { await runECSqlSyncReaderTest(test, dataset); }); } else { it(`${test.fileName}: ${test.title} (ECSqlReader)`, async () => { await runECSqlReaderTest(test, dataset); }); it(`${test.fileName}: ${test.title} (ECSqlSyncReader)`, async () => { await runECSqlSyncReaderTest(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 "range3d": const parsedRange3d = JSON.parse(binder.value); stmt.bindRange3d(id, { low: { x: parsedRange3d.low.x, y: parsedRange3d.low.y, z: parsedRange3d.low.z }, high: { x: parsedRange3d.high.x, y: parsedRange3d.high.y, z: parsedRange3d.high.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: // eslint-disable-next-line @typescript-eslint/no-deprecated return QueryRowFormat.UseJsPropertyNames; default: return QueryRowFormat.UseECSqlPropertyNames; } } /** Builds a QueryBinder from the test's binder definitions. Returns undefined when the test has no binders. */ function buildQueryBinder(test) { if (test.binders === undefined) return undefined; const 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 "range3d": const parsedRange3d = JSON.parse(binder.value); params.bindRange3d(id, { low: { x: parsedRange3d.low.x, y: parsedRange3d.low.y, z: parsedRange3d.low.z }, high: { x: parsedRange3d.high.x, y: parsedRange3d.high.y, z: parsedRange3d.high.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 return params; } /** Builds the common query options object from the test configuration. */ function buildReaderQueryOptions(test) { 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; } return queryOptions; } /** * Shared assertion logic for QueryReader tests that operates on the result rows and column metadata. * Used by both ECSqlReader and ECSqlSyncReader test paths. */ function runResultAssertions(test, rows, colMetaData) { let resultCount = 0; 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 (test.expectedResults && test.expectedResults.length !== resultCount) { assert.fail(`Expected ${test.expectedResults.length} rows but got ${resultCount}`); } } /** * Shared assertion logic that operates on an already-created ECSqlReader. * Used by ECSqlReader test paths. */ async function runAssertionsOnReader(test, reader, label) { let rows; try { rows = await reader.toArray(); } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during execution of ${label}: ${error.message}`); } if (test.errorDuringPrepare) assert.fail(`Statement is expected to fail during prepare`); const colMetaData = await reader.getMetaData(); runResultAssertions(test, rows, colMetaData); } /** * Shared assertion logic that operates on an already-created ECSqlReader. * Used by ECSqlSyncReader test paths. */ function runAssertionsOnSyncReader(test, reader, label) { let rows; try { rows = reader.toArray(); } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during execution of ${label}: ${error.message}`); } if (test.errorDuringPrepare) assert.fail(`Statement is expected to fail during prepare`); const colMetaData = reader.getMetaData(); runResultAssertions(test, rows, colMetaData); } async function runECSqlReaderTest(test, dataset) { const imodel = snapshotDbs[dataset]; if (!imodel) { assert.fail(`Dataset ${dataset} is not loaded`); } if (test.sql === undefined) { assert.fail("Test does not have an ECSql statement"); } const params = buildQueryBinder(test); const queryOptions = buildReaderQueryOptions(test); let reader; try { reader = imodel.createQueryReader(test.sql, params, queryOptions); } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during creating ECSqlReader: ${error.message}`); } await runAssertionsOnReader(test, reader, "ECSqlReader"); } async function runECSqlSyncReaderTest(test, dataset) { const imodel = snapshotDbs[dataset]; if (!imodel) { assert.fail(`Dataset ${dataset} is not loaded`); } if (test.sql === undefined) { assert.fail("Test does not have an ECSql statement"); } const params = buildQueryBinder(test); const queryOptions = buildReaderQueryOptions(test); try { imodel.withQueryReader(test.sql, reader => { runAssertionsOnSyncReader(test, reader, "ECSqlSyncReader"); }, params, queryOptions); } catch (error) { if (test.errorDuringPrepare) return; else assert.fail(`Error during creating ECSqlSyncReader: ${error.message}`); } } function checkingExpectedResults(rowFormat, actualResult, expectedResult, indexesToInclude) { if (rowFormat === ECDbTestRowFormat.ECSqlIndexes && indexesToInclude) { let i = 0; for (const key of Object.keys(expectedResult)) { const expected = typeof expectedResult[key] === "string" ? expectedResult[key].trim() : expectedResult[key]; const actual = typeof actualResult[indexesToInclude[i]] === "string" ? actualResult[indexesToInclude[i]].trim() : actualResult[indexesToInclude[i]]; assert.deepEqual(actual, expected, `Expected ${JSON.stringify(expectedResult[key])} but got ${JSON.stringify(actualResult[indexesToInclude[i]])}`); i++; } } else { for (const key of Object.keys(expectedResult)) { const expected = typeof expectedResult[key] === "string" ? expectedResult[key].trim() : expectedResult[key]; const actual = typeof actualResult[key] === "string" ? actualResult[key].trim() : actualResult[key]; assert.deepEqual(actual, expected, `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