Skip to content

Commit

Permalink
[spruce] Add bun support in CI (#184)
Browse files Browse the repository at this point in the history
## Problem
Bun has been a rapidly emerging toolkit for developing, testing,
running, and bundling Javascript and TypeScript. We currently have an
outstanding GH issue related to the bun runtime:
#141. It would
be nice to add some coverage in CI for running our integration tests
with bun as the runner.

@jhamon kicked this work off a while ago and I wanted to make sure we
got this included.

## Solution
- Update `/github/workflows/testing.yml` and add a new `config` section
to the `integration-tests` matrix. Tests will now be run with `npm` in
node and edge, and `bun` with node for bun versions `1.0.4` -> `1.0.7`.

## Type of Change
- [X] Infrastructure change (CI configs, etc)

## Test Plan
Make sure all the integration tests are 🟢 in CI.
  • Loading branch information
austin-denoble committed Jan 13, 2024
1 parent 4ca7c01 commit 1b08e68
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 72 deletions.
53 changes: 45 additions & 8 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,21 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1
max-parallel: 2
matrix:
jest_env: ['node', 'edge']
pinecone_env:
- prod
# - staging
config:
[
{ runner: 'npm', jest_env: 'node' },
{ runner: 'npm', jest_env: 'edge' },
{ runner: 'bun', jest_env: 'node', bun_version: '1.0.0' },
{ runner: 'bun', jest_env: 'node', bun_version: '1.0.5' },
{ runner: 'bun', jest_env: 'node', bun_version: '1.0.10' },
{ runner: 'bun', jest_env: 'node', bun_version: '1.0.15' },
{ runner: 'bun', jest_env: 'node', bun_version: '1.0.20' },
]

steps:
- name: Checkout
Expand All @@ -38,21 +47,49 @@ jobs:
- name: Setup
uses: ./.github/actions/setup

- name: Run integration tests (prod)
- name: Setup bun
if: matrix.config.bun_version
uses: oven-sh/setup-bun@v1
with:
bun-version: ${{ matrix.config.bun_version }}

- name: Run integration tests (Prod)
if: matrix.pinecone_env == 'prod'
env:
CI: true
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
PINECONE_CONTROLLER_HOST: 'https://api.pinecone.io'
run: npm run test:integration:${{ matrix.jest_env }}
run: ${{ matrix.config.runner }} run test:integration:${{ matrix.config.jest_env }}

- name: Run integration tests (staging)
- name: Run integration tests (Staging)
if: matrix.pinecone_env == 'staging'
env:
CI: true
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY_STAGING }}
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
PINECONE_CONTROLLER_HOST: 'https://api-staging.pinecone.io'
run: npm run test:integration:${{ matrix.jest_env }}
run: ${{ matrix.config.runner }} run test:integration:${{ matrix.config.jest_env }}

integration-test-cleanup:
name: Clean up integration tests
if: always()
needs: [integration-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18.x
registry-url: 'https://registry.npmjs.org'
- name: Install npm packages
run: |
npm install --ignore-scripts
shell: bash
- name: Run integration cleanup command
env:
CI: true
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
run: npm run test:integration:cleanup

typescript-compilation-tests:
name: TS compile
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
testPathIgnorePatterns: ['src/integration'],
testTimeout: 100000,
testTimeout: 150000,
verbose: true,
detectOpenHandles: false,
collectCoverageFrom: [
Expand Down
139 changes: 76 additions & 63 deletions src/integration/data/delete.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Pinecone, Index } from '../../index';
import {
assertWithRetries,
randomString,
generateRecords,
INDEX_NAME,
sleep,
waitUntilRecordsReady,
} from '../test-helpers';

Expand Down Expand Up @@ -49,39 +49,39 @@ describe('delete', () => {
await ns.upsert(recordToUpsert);

// Await record freshness, and check record upserted
let stats = await waitUntilRecordsReady(ns, namespace, recordIds);
const stats = await waitUntilRecordsReady(ns, namespace, recordIds);
if (stats.namespaces) {
expect(stats.namespaces[namespace].recordCount).toEqual(1);
} else {
fail('Expected namespaces to be defined');
}

// Look more closely at one of the records to make sure values set
const fetchResult = await ns.fetch(['0']);
const records = fetchResult.records;
if (records) {
expect(records['0'].id).toEqual('0');
expect(records['0'].values.length).toEqual(5);
} else {
fail(
'Did not find expected records. Fetch result was ' +
JSON.stringify(fetchResult)
);
}
const fetchAssertions = [
(results) => {
if (results.records) {
expect(results.records['0'].id).toEqual('0');
expect(results.records['0'].values.length).toEqual(5);
} else {
fail(
'Did not find expected records. Fetch result was ' +
JSON.stringify(results)
);
}
},
];
assertWithRetries(() => ns.fetch(['0']), fetchAssertions);

// Try deleting the record
await ns.deleteOne('0');
await sleep(3000);

// Verify the record is removed
stats = await ns.describeIndexStats();
if (stats.namespaces) {
expect(stats.namespaces[namespace]).toBeUndefined();
} else {
// no-op. This shouldn't actually happen unless there
// are leftover namespaces from previous runs that
// failed or stopped without proper cleanup.
}
const deleteAssertions = [
(stats) => {
expect(stats.namespaces[namespace]).toBeUndefined();
},
];
assertWithRetries(() => ns.describeIndexStats(), deleteAssertions);
});

test('verify deleteMany with ids', async () => {
Expand All @@ -95,63 +95,76 @@ describe('delete', () => {
await ns.upsert(recordsToUpsert);

// Await record freshness, and check records upserted
let stats = await waitUntilRecordsReady(ns, namespace, recordIds);
const stats = await waitUntilRecordsReady(ns, namespace, recordIds);
if (stats.namespaces) {
expect(stats.namespaces[namespace].recordCount).toEqual(3);
} else {
fail('Expected namespaces to be defined');
}

// Look more closely at one of the records to make sure values set
const fetchResult = await ns.fetch(['0']);
const records = fetchResult.records;
if (records) {
expect(records['0'].id).toEqual('0');
expect(records['0'].values.length).toEqual(5);
} else {
fail(
'Did not find expected records. Fetch result was ' +
JSON.stringify(fetchResult)
);
}
const fetchAssertions = [
(results) => {
if (results.records) {
expect(results.records['0'].id).toEqual('0');
expect(results.records['0'].values.length).toEqual(5);
} else {
fail(
'Did not find expected records. Fetch result was ' +
JSON.stringify(results)
);
}
},
];
assertWithRetries(() => ns.fetch(['0']), fetchAssertions);

// Try deleting 2 of 3 records
await ns.deleteMany(['0', '2']);
await sleep(3000);
stats = await ns.describeIndexStats();
if (stats.namespaces) {
expect(stats.namespaces[namespace].recordCount).toEqual(1);
} else {
fail(
'Expected namespaces to be defined (second call). Stats were ' +
JSON.stringify(stats)
);
}

const deleteAssertions = [
(stats) => {
if (stats.namespaces) {
expect(stats.namespaces[namespace].recordCount).toEqual(1);
} else {
fail(
'Expected namespaces to be defined (second call). Stats were ' +
JSON.stringify(stats)
);
}
},
];
assertWithRetries(() => ns.describeIndexStats(), deleteAssertions);

// Check that record id='1' still exists
const fetchResult2 = await ns.fetch(['1']);
const records2 = fetchResult2.records;
if (records2) {
expect(records2['1']).not.toBeUndefined();
} else {
fail(
'Expected record 2 to be defined. Fetch result was ' +
JSON.stringify(fetchResult2)
);
}
const fetchAssertions2 = [
(results) => {
if (results.records2) {
expect(results.records2['1']).not.toBeUndefined();
} else {
fail(
'Expected record 2 to be defined. Fetch result was ' +
JSON.stringify(results)
);
}
},
];
assertWithRetries(() => ns.fetch(['1']), fetchAssertions2);

// deleting non-existent records should not throw
await ns.deleteMany(['0', '1', '2', '3']);
await sleep(3000);

// Verify all are now removed
stats = await ns.describeIndexStats();
if (stats.namespaces) {
expect(stats.namespaces[namespace]).toBeUndefined();
} else {
// no-op. This shouldn't actually happen unless there
// are leftover namespaces from previous runs that
// failed or stopped without proper cleanup.
}
const deleteAssertions2 = [
(stats) => {
if (stats.namespaces) {
expect(stats.namespaces[namespace]).toBeUndefined();
} else {
// no-op. This shouldn't actually happen unless there
// are leftover namespaces from previous runs that
// failed or stopped without proper cleanup.
}
},
];
assertWithRetries(() => ns.describeIndexStats(), deleteAssertions2);
});
});
22 changes: 22 additions & 0 deletions src/integration/data/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,26 @@ describe('query', () => {
assertions
);
});

test('query with includeValues: true', async () => {
const recordsToUpsert = generateRecords(5, 3);
expect(recordsToUpsert).toHaveLength(3);

const queryVec = Array.from({ length: 5 }, () => Math.random());

const assertions = [
(results) => expect(results.matches).toBeDefined(),
(results) => expect(results.matches?.length).toEqual(2),
];
await assertWithRetries(
() =>
ns.query({
vector: queryVec,
topK: 2,
includeValues: true,
includeMetadata: true,
}),
assertions
);
});
});

0 comments on commit 1b08e68

Please sign in to comment.