|
| 1 | +import { |
| 2 | + deserializeQueryHistory, |
| 3 | + serializeQueryHistory, |
| 4 | +} from "../../../src/query-serialization"; |
| 5 | +import { join } from "path"; |
| 6 | +import { writeFileSync, mkdirpSync } from "fs-extra"; |
| 7 | +import { LocalQueryInfo, InitialQueryInfo } from "../../../src/query-results"; |
| 8 | +import { QueryWithResults } from "../../../src/run-queries-shared"; |
| 9 | +import { DatabaseInfo } from "../../../src/pure/interface-types"; |
| 10 | +import { CancellationTokenSource, Uri } from "vscode"; |
| 11 | +import { tmpDir } from "../../../src/helpers"; |
| 12 | +import { QueryResultType } from "../../../src/pure/legacy-messages"; |
| 13 | +import { QueryInProgress } from "../../../src/legacy-query-server/run-queries"; |
| 14 | + |
| 15 | +describe("serialize and deserialize", () => { |
| 16 | + let infoSuccessRaw: LocalQueryInfo; |
| 17 | + let infoSuccessInterpreted: LocalQueryInfo; |
| 18 | + let infoEarlyFailure: LocalQueryInfo; |
| 19 | + let infoLateFailure: LocalQueryInfo; |
| 20 | + let infoInprogress: LocalQueryInfo; |
| 21 | + let allHistory: LocalQueryInfo[]; |
| 22 | + let queryPath: string; |
| 23 | + let cnt = 0; |
| 24 | + |
| 25 | + beforeEach(() => { |
| 26 | + queryPath = join(Uri.file(tmpDir.name).fsPath, `query-${cnt++}`); |
| 27 | + |
| 28 | + infoSuccessRaw = createMockFullQueryInfo( |
| 29 | + "a", |
| 30 | + createMockQueryWithResults( |
| 31 | + `${queryPath}-a`, |
| 32 | + false, |
| 33 | + false, |
| 34 | + "/a/b/c/a", |
| 35 | + false, |
| 36 | + ), |
| 37 | + ); |
| 38 | + infoSuccessInterpreted = createMockFullQueryInfo( |
| 39 | + "b", |
| 40 | + createMockQueryWithResults( |
| 41 | + `${queryPath}-b`, |
| 42 | + true, |
| 43 | + true, |
| 44 | + "/a/b/c/b", |
| 45 | + false, |
| 46 | + ), |
| 47 | + ); |
| 48 | + infoEarlyFailure = createMockFullQueryInfo("c", undefined, true); |
| 49 | + infoLateFailure = createMockFullQueryInfo( |
| 50 | + "d", |
| 51 | + createMockQueryWithResults( |
| 52 | + `${queryPath}-c`, |
| 53 | + false, |
| 54 | + false, |
| 55 | + "/a/b/c/d", |
| 56 | + false, |
| 57 | + ), |
| 58 | + ); |
| 59 | + infoInprogress = createMockFullQueryInfo("e"); |
| 60 | + allHistory = [ |
| 61 | + infoSuccessRaw, |
| 62 | + infoSuccessInterpreted, |
| 63 | + infoEarlyFailure, |
| 64 | + infoLateFailure, |
| 65 | + infoInprogress, |
| 66 | + ]; |
| 67 | + }); |
| 68 | + |
| 69 | + it("should serialize and deserialize query history", async () => { |
| 70 | + // the expected results only contains the history with completed queries |
| 71 | + const expectedHistory = [ |
| 72 | + infoSuccessRaw, |
| 73 | + infoSuccessInterpreted, |
| 74 | + infoLateFailure, |
| 75 | + ]; |
| 76 | + |
| 77 | + const allHistoryPath = join(tmpDir.name, "workspace-query-history.json"); |
| 78 | + |
| 79 | + // serialize and deserialize |
| 80 | + await serializeQueryHistory(allHistory, allHistoryPath); |
| 81 | + const allHistoryActual = await deserializeQueryHistory(allHistoryPath); |
| 82 | + |
| 83 | + // the dispose methods will be different. Ignore them. |
| 84 | + allHistoryActual.forEach((info) => { |
| 85 | + if (info.t === "local" && info.completedQuery) { |
| 86 | + const completedQuery = info.completedQuery; |
| 87 | + (completedQuery as any).dispose = undefined; |
| 88 | + |
| 89 | + // these fields should be missing on the deserialized value |
| 90 | + // but they are undefined on the original value |
| 91 | + if (!("logFileLocation" in completedQuery)) { |
| 92 | + (completedQuery as any).logFileLocation = undefined; |
| 93 | + } |
| 94 | + const query = completedQuery.query; |
| 95 | + if (!("quickEvalPosition" in query)) { |
| 96 | + (query as any).quickEvalPosition = undefined; |
| 97 | + } |
| 98 | + } |
| 99 | + }); |
| 100 | + expectedHistory.forEach((info) => { |
| 101 | + if (info.completedQuery) { |
| 102 | + (info.completedQuery as any).dispose = undefined; |
| 103 | + } |
| 104 | + }); |
| 105 | + |
| 106 | + // make the diffs somewhat sane by comparing each element directly |
| 107 | + for (let i = 0; i < allHistoryActual.length; i++) { |
| 108 | + expect(allHistoryActual[i]).toEqual(expectedHistory[i]); |
| 109 | + } |
| 110 | + expect(allHistoryActual.length).toEqual(expectedHistory.length); |
| 111 | + }); |
| 112 | + |
| 113 | + it("should handle an invalid query history version", async () => { |
| 114 | + const badPath = join(tmpDir.name, "bad-query-history.json"); |
| 115 | + writeFileSync( |
| 116 | + badPath, |
| 117 | + JSON.stringify({ |
| 118 | + version: 3, |
| 119 | + queries: allHistory, |
| 120 | + }), |
| 121 | + "utf8", |
| 122 | + ); |
| 123 | + |
| 124 | + const allHistoryActual = await deserializeQueryHistory(badPath); |
| 125 | + // version number is invalid. Should return an empty array. |
| 126 | + expect(allHistoryActual).toEqual([]); |
| 127 | + }); |
| 128 | + |
| 129 | + function createMockFullQueryInfo( |
| 130 | + dbName = "a", |
| 131 | + queryWithResults?: QueryWithResults, |
| 132 | + isFail = false, |
| 133 | + ): LocalQueryInfo { |
| 134 | + const fqi = new LocalQueryInfo( |
| 135 | + { |
| 136 | + databaseInfo: { |
| 137 | + name: dbName, |
| 138 | + databaseUri: Uri.parse(`/a/b/c/${dbName}`).fsPath, |
| 139 | + } as unknown as DatabaseInfo, |
| 140 | + start: new Date(), |
| 141 | + queryPath: "path/to/hucairz", |
| 142 | + queryText: "some query", |
| 143 | + isQuickQuery: false, |
| 144 | + isQuickEval: false, |
| 145 | + id: `some-id-${dbName}`, |
| 146 | + } as InitialQueryInfo, |
| 147 | + { |
| 148 | + dispose: () => { |
| 149 | + /**/ |
| 150 | + }, |
| 151 | + } as CancellationTokenSource, |
| 152 | + ); |
| 153 | + |
| 154 | + if (queryWithResults) { |
| 155 | + fqi.completeThisQuery(queryWithResults); |
| 156 | + } |
| 157 | + if (isFail) { |
| 158 | + fqi.failureReason = "failure reason"; |
| 159 | + } |
| 160 | + return fqi; |
| 161 | + } |
| 162 | + |
| 163 | + function createMockQueryWithResults( |
| 164 | + queryPath: string, |
| 165 | + didRunSuccessfully = true, |
| 166 | + hasInterpretedResults = true, |
| 167 | + dbPath = "/a/b/c", |
| 168 | + includeSpies = true, |
| 169 | + ): QueryWithResults { |
| 170 | + // pretend that the results path exists |
| 171 | + const resultsPath = join(queryPath, "results.bqrs"); |
| 172 | + mkdirpSync(queryPath); |
| 173 | + writeFileSync(resultsPath, "", "utf8"); |
| 174 | + |
| 175 | + const query = new QueryInProgress( |
| 176 | + queryPath, |
| 177 | + Uri.file(dbPath).fsPath, |
| 178 | + true, |
| 179 | + "queryDbscheme", |
| 180 | + undefined, |
| 181 | + { |
| 182 | + name: "vwx", |
| 183 | + }, |
| 184 | + ); |
| 185 | + |
| 186 | + const result: QueryWithResults = { |
| 187 | + query: query.queryEvalInfo, |
| 188 | + successful: didRunSuccessfully, |
| 189 | + message: "foo", |
| 190 | + dispose: jest.fn(), |
| 191 | + result: { |
| 192 | + evaluationTime: 1, |
| 193 | + queryId: 0, |
| 194 | + runId: 0, |
| 195 | + resultType: QueryResultType.SUCCESS, |
| 196 | + }, |
| 197 | + }; |
| 198 | + |
| 199 | + if (includeSpies) { |
| 200 | + (query as any).hasInterpretedResults = () => |
| 201 | + Promise.resolve(hasInterpretedResults); |
| 202 | + } |
| 203 | + |
| 204 | + return result; |
| 205 | + } |
| 206 | +}); |
0 commit comments