blob: 8cff5998db9ed221235119b4840b652ea3e6a3d2 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 '../jest-extensions.js';
import {
GeneratedTable,
GeneratedRecordBatch,
GeneratedVector
} from '../generate-test-data.js';
import { RecordBatch, Schema, Vector, util } from 'apache-arrow';
const { createElementComparator: compare } = util;
export function validateTable({ keys, rows, cols, rowBatches, colBatches, keyBatches, table }: GeneratedTable) {
describe(`Table: ${table.schema}`, () => {
validateVector({ values: rows, vector: new Vector(table.data) });
for (const [i, data] of table.data.entries()) {
describe(`recordBatch ${i}`, () => {
validateRecordBatch({
keys: keyBatches[i], rows: rowBatches[i], cols: colBatches[i],
recordBatch: new RecordBatch(new Schema(data.type.children), data)
});
});
}
for (const [i, field] of table.schema.fields.entries()) {
describe(`column ${i}: ${field}`, () => {
validateVector({
keys: keys()[i],
values: () => cols()[i],
vector: table.getChildAt(i)!
});
});
}
});
}
export function validateRecordBatch({ rows, cols, keys, recordBatch }: GeneratedRecordBatch) {
describe(`RecordBatch: ${recordBatch.schema}`, () => {
validateVector({ values: rows, vector: new Vector([recordBatch.data]) });
for (const [i, field] of recordBatch.schema.fields.entries()) {
describe(`Field: ${field}`, () => {
validateVector({
keys: keys()[i],
values: () => cols()[i],
vector: recordBatch.getChildAt(i)!
});
});
}
});
}
export function validateVector({ values: createTestValues, vector, keys }: GeneratedVector<any>) {
const values = createTestValues();
const begin = Math.trunc(values.length * .25);
const end = Math.trunc(values.length * .75);
describe(`Vector<${vector.type}>`, () => {
// test no slice
describe(`sliced=false`, () => { vectorTests(values, vector, keys); });
// test slice with no args
describe(`sliced=true, begin=, end=`, () => {
vectorTests(
values.slice(), // values,
vector.slice(), // vector,
keys ? keys.slice() : undefined // keys
);
});
// test slicing half the array
describe(`sliced=true, begin=${begin}, end=${end}`, () => {
vectorTests(
values.slice(begin, end), // values,
vector.slice(begin, end), // vector,
keys ? keys.slice(begin, end) : undefined // keys
);
});
// test concat each end together
describe(`sliced=true, begin=${end}, end=${begin}, concat=true`, () => {
vectorTests(
values.slice(0, begin).concat(values.slice(end)), // values,
vector.slice(0, begin).concat(vector.slice(end)), // vector,
keys ? [...keys.slice(0, begin), ...keys.slice(end)] : undefined // keys
);
});
});
}
function vectorTests(values: any[], vector: Vector<any>, keys?: number[]) {
test(`length is correct`, () => {
expect(vector).toHaveLength(values.length);
});
test(`gets expected values`, () => {
expect.hasAssertions();
let i = -1, actual, expected;
const n = vector.length;
try {
while (++i < n) {
actual = vector.get(i);
expected = values[i];
expect(actual).toArrowCompare(expected);
}
} catch (e: any) {
throw new Error(`${vector}[${i}]:\n\t${e && e.stack || e}`);
}
});
if (keys && keys.length > 0) {
test(`dictionary indices should match`, () => {
expect.hasAssertions();
const indices = new Vector(vector.data.map((data) => data.clone(vector.type.indices)));
let i = -1;
const n = indices.length;
try {
while (++i < n) {
indices.isValid(i)
? expect(indices.get(i)).toBe(keys[i])
: expect(indices.get(i)).toBeNull();
}
} catch (e) {
throw new Error(`${indices}[${i}]: ${e}`);
}
});
}
test(`sets expected values`, () => {
expect.hasAssertions();
let i = -1, actual, expected;
const n = vector.length;
try {
while (++i < n) {
expected = values[i];
vector.set(i, expected);
actual = vector.get(i);
expect(actual).toArrowCompare(expected);
}
} catch (e: any) {
throw new Error(`${vector}[${i}]:\n\t${e && e.stack || e}`);
}
});
test(`iterates expected values`, () => {
expect.hasAssertions();
let i = -1, actual, expected;
try {
for (actual of vector) {
expected = values[++i];
expect(actual).toArrowCompare(expected);
}
} catch (e: any) {
throw new Error(`${vector}[${i}]:\n\t${e && e.stack || e}`);
}
});
test(`indexOf returns expected values`, () => {
expect.hasAssertions();
let i = -1;
const n = vector.length;
const shuffled = shuffle(values);
let value: any, actual, expected;
try {
while (++i < n) {
value = shuffled[i];
actual = vector.indexOf(value);
expected = values.findIndex(compare(value));
expect(actual).toBe(expected);
// eslint-disable-next-line jest/prefer-to-contain
expect(vector.includes(value)).toBe(true);
}
// I would be pretty surprised if randomatic ever generates these values
expect(vector.indexOf('purple elephants')).toBe(-1);
expect(vector.indexOf('whistling wombats')).toBe(-1);
expect(vector.indexOf('carnivorous novices')).toBe(-1);
} catch (e: any) {
throw new Error(`${vector}[${i}]:\n\t${e && e.stack || e}`);
}
});
}
function shuffle(input: any[]) {
const result = input.slice();
let j, tmp, i = result.length;
while (--i > 0) {
j = Math.trunc(Math.random() * (i + 1));
tmp = result[i];
result[i] = result[j];
result[j] = tmp;
}
return result;
}