Skip to content

Commit

Permalink
refactor: deduplicate backend unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dvirtz committed Sep 20, 2023
1 parent 6b6c7d3 commit ad14d07
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 216 deletions.
3 changes: 3 additions & 0 deletions src/backend-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// https://stackoverflow.com/a/64174790/621176
export const BackendNames = ['parquet-tools', 'parquets', 'arrow'] as const;
export type BackendName = typeof BackendNames[number];
15 changes: 15 additions & 0 deletions src/parquet-backend-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ArrowBackend } from './arrow-backend';
import { BackendName } from './backend-name';
import { ParquetToolsBackend } from './parquet-tools-backend';
import { ParquetsBackend } from './parquets-backend';

export function createParquetBackend(backend: BackendName) {
switch (backend) {
case 'parquet-tools':
return new ParquetToolsBackend;
case 'parquets':
return new ParquetsBackend;
default:
return new ArrowBackend;
}
}
17 changes: 3 additions & 14 deletions src/parquet-document.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { ArrowBackend } from './arrow-backend';
import { ParquetToolsBackend } from './parquet-tools-backend';
import { ParquetsBackend } from './parquets-backend';
import { backend } from './settings';
import { assert } from 'console';
import { promises } from 'fs';
import { getLogger } from './logger';
import { createParquetBackend } from './parquet-backend-factory';
import { backend } from './settings';

export default class ParquetDocument implements vscode.Disposable {
private readonly _uri: vscode.Uri;
Expand All @@ -17,16 +15,7 @@ export default class ParquetDocument implements vscode.Disposable {
private readonly _disposables: vscode.Disposable[] = [];
private readonly _parquetPath: string;
private _lastMod = 0;
private readonly _backend = (() => {
switch (backend()) {
case 'parquet-tools':
return new ParquetToolsBackend;
case 'parquets':
return new ParquetsBackend;
default:
return new ArrowBackend;
}
})();
private readonly _backend = createParquetBackend(backend());


private constructor(uri: vscode.Uri, emitter: vscode.EventEmitter<vscode.Uri>) {
Expand Down
6 changes: 4 additions & 2 deletions src/parquet-tools-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { spawn } from "child_process";
import * as path from 'path';
import { strict as assert } from 'assert';
import { getLogger } from './logger';
import { parquetTools as getParquetTools } from './settings';
import { parquetTools as getParquetTools, jsonSpace } from './settings';
import { createInterface } from 'readline';
import { ParquetBackend } from './parquet-backend';

Expand Down Expand Up @@ -55,6 +55,8 @@ export class ParquetToolsBackend extends ParquetBackend {
}

public async * toJsonImpl(parquetPath: string, token?: vscode.CancellationToken): AsyncGenerator<string> {
yield* ParquetToolsBackend.spawnParquetTools(['cat', '-j', parquetPath], token);
for await (const line of ParquetToolsBackend.spawnParquetTools(['cat', '-j', parquetPath], token)) {
yield JSON.stringify(JSON.parse(line), null, jsonSpace());
}
}
}
5 changes: 2 additions & 3 deletions src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { LogLevel } from '@vscode-logging/logger';
import { BackendName } from './backend-name';

const basename = 'parquet-viewer';

Expand Down Expand Up @@ -49,8 +50,6 @@ export function jsonSpace(): number | string | undefined {

export const loggingSettings = ['logging', 'logLevel', 'logPanel', 'logFolder'].map(s => `${basename}.${s}`);

export type Backend = 'parquet-tools' | 'parquets' | 'arrow';

export function backend(): Backend {
export function backend(): BackendName {
return useParquetTools() ? 'parquet-tools' : settings().get('backend', 'parquets');
}
11 changes: 6 additions & 5 deletions test/integration/parquet-editor-provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {describe, expect, test, jest, beforeEach, afterEach} from '@jest/globals';
import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals';
import * as path from 'path';
import * as vscode from 'vscode';
import { BackendName } from '../../src/backend-name';
import { initLogger } from '../../src/logger';
import { ParquetTextDocumentContentProvider } from "../../src/parquet-document-provider";
import { ParquetEditorProvider } from "../../src/parquet-editor-provider";
import * as settings from '../../src/settings';
import { getUri, readFile } from "./utils";
import * as path from 'path';
import { initLogger } from '../../src/logger';

jest.setTimeout(60000);

Expand Down Expand Up @@ -53,12 +54,12 @@ describe('ParquetEditorProvider', function () {
return uri;
}

test.each<[string, settings.Backend]>([
test.each<[string, BackendName]>([
['small', 'parquet-tools'],
['small', 'parquets'],
['small', 'arrow'],
['large', 'parquets']
])('shows %p using %p', async function (name: string, backend: settings.Backend) {
])('shows %p using %p', async function (name, backend) {
const parquet = await getUri(`${name}.parquet`);
testFile = await copyTo(`${name}-${backend}.parquet`, parquet);
const checkChanged = new Promise(resolve => {
Expand Down
20 changes: 20 additions & 0 deletions test/unit/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

import { jest } from '@jest/globals';
import * as path from 'path';

const testDir = path.join(__dirname, '..', '..');

module.exports = {
workspace: {
getConfiguration: jest.fn().mockReturnValue({
get: jest.fn(name => {
switch (name) {
case 'parquetToolsPath':
return path.join(testDir, 'workspace', 'parquet-tools-1.12.0-SNAPSHOT.jar');
default:
return undefined;
}
})
}),
}
};
68 changes: 0 additions & 68 deletions test/unit/arrow-backend.test.ts

This file was deleted.

93 changes: 93 additions & 0 deletions test/unit/backend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import toArray from '@async-generators/to-array';
import { afterEach, describe, expect, jest, test } from '@jest/globals';
import { createReadStream } from 'fs';
import * as path from 'path';
import { createInterface } from 'readline';
import { CancellationToken } from 'vscode';
import { BackendNames } from '../../src/backend-name';
import { createParquetBackend } from '../../src/parquet-backend-factory';
import * as vscode from 'vscode';

jest.setTimeout(60000);

const rootDir = path.join(__dirname, '..', '..');

describe.each(BackendNames)("%s backend tests", (backendName) => {
const backend = createParquetBackend(backendName);
const workspace = path.join(rootDir, 'test', 'workspace');

const testFiles = {
'parquet-tools': [
['small', 'small'],
['large', 'large'],
],
'parquets': [
['small', 'small'],
['large', 'large'],
],
'arrow': [
['small', 'small'],
['large', 'large.arrow'],
]
};

test.each(
testFiles[backendName]
)('Converts %s parquet to JSON', async function (input: string, expectedFile: string) {
const actual = (await toArray(backend.toJson(path.join(workspace, `${input}.parquet`)))).map(line => line.trim());
const expected = await toArray(createInterface({ input: createReadStream(path.join(workspace, `${expectedFile}.json`)) }));

expect(actual).toEqual(expected);
});

test("Error on not existing file", async function () {
const error = (() => {
switch (backendName) {
case 'arrow':
return "Failed to open no-such-file: Failed to open local file 'no-such-file'";
case 'parquets':
return /ENOENT: no such file or directory, stat '.*no-such-file'/;
case 'parquet-tools':
return /parquet-tools exited with code 1\n.*java.io.FileNotFoundException: File no-such-file does not exist/s;
}
})();
await expect(toArray(backend.toJson("no-such-file"))).rejects.toThrow(error);
});

test("cancellation", async function () {
const token = {
get isCancellationRequested() {
return this.isCancellationRequestedMock();
},
isCancellationRequestedMock: jest.fn().mockReturnValueOnce(false).mockReturnValue(true),
onCancellationRequested: jest.fn()
};
expect(await toArray(backend.toJson(path.join(workspace, `small.parquet`), token as CancellationToken))).toHaveLength(1);
expect(token.isCancellationRequestedMock).toBeCalledTimes(2);
});

describe('mocked jsonSpace', () => {
const workspaceConfig = vscode.workspace.getConfiguration();
const parquetToolsPath = workspaceConfig.get('parquetToolsPath');
let mockedSpaced: jest.Mocked<typeof workspaceConfig.get>;

test.each([0, 2, 10, "\t", "###"])('Test space %s', async function (space: number | string) {
mockedSpaced = jest.mocked(workspaceConfig.get).mockImplementation(name => {
return {
'jsonSpace': space,
'parquetToolsPath': parquetToolsPath
}[name];
});

const json = (await toArray(backend.toJson(path.join(workspace, `small.parquet`)))).map(line => line.trim());
const records = await toArray(createInterface({ input: createReadStream(path.join(workspace, `small.json`)) }));
const expected = records.map(record => JSON.stringify(JSON.parse(record), null, space));

expect(json).toEqual(expected);
});

afterEach(() => {
mockedSpaced.mockRestore();
});
});
});
3 changes: 2 additions & 1 deletion test/unit/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const config: Config.InitialOptions = {
...common,
moduleNameMapper: {
'^vscode$': '<rootDir>/../../node_modules/@types/vscode/index.d.ts'
}
},
modulePathIgnorePatterns: []
};

export default config;
59 changes: 1 addition & 58 deletions test/unit/parquet-tools-backend.test.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,9 @@
import {describe, expect, test, jest} from '@jest/globals';
import toArray from '@async-generators/to-array';
import { createReadStream } from 'fs';
import * as path from 'path';
import { createInterface } from "readline";
import { describe, expect, test } from '@jest/globals';
import { ParquetToolsBackend } from "../../src/parquet-tools-backend";
import { CancellationToken } from 'vscode';

const rootDir = path.join(__dirname, '..', '..');

jest.setTimeout(60000);

jest.mock('vscode', () => {
const getConfigurationMock = jest.fn();
getConfigurationMock.mockReturnValue({
get: jest.fn(name => {
switch (name) {
case 'parquetToolsPath':
return path.join(rootDir, 'test', 'workspace', 'parquet-tools-1.12.0-SNAPSHOT.jar');
default:
return undefined;
}
})
});
return {
workspace: {
getConfiguration: getConfigurationMock,
}
};
});

describe("ParquetToolsBackend tests", () => {
const backend = new ParquetToolsBackend();
const workspace = path.join(rootDir, 'test', 'workspace');

test.each(
["small", "large"]
)('Converts %s parquet to JSON', async function (name: string) {
const json = (await toArray(backend.toJson(path.join(workspace, `${name}.parquet`))));
const expected = await toArray(createInterface({ input: createReadStream(path.join(workspace, `${name}.json`)) }));

expect(json).toEqual(expected);
});

test("Error on not existing file", async function () {
await expect(toArray(backend.toJson("no-such-file"))).rejects.toThrow(
/parquet-tools exited with code 1\n.*java.io.FileNotFoundException: File no-such-file does not exist/s);
});

test("-h works", async function () {
const stdout = (await ParquetToolsBackend.spawnParquetTools(['-h']).next()).value;
expect(stdout).toContain('parquet-tools cat:');
});

test("cancellation", async function () {
const token = {
get isCancellationRequested() {
return this.isCancellationRequestedMock();
},
isCancellationRequestedMock: jest.fn().mockReturnValueOnce(false).mockReturnValue(true),
onCancellationRequested: jest.fn()
};
expect(await toArray(backend.toJson(path.join(workspace, `small.parquet`), token as CancellationToken))).toHaveLength(1);
expect(token.isCancellationRequestedMock).toBeCalledTimes(2);
});
});
Loading

0 comments on commit ad14d07

Please sign in to comment.