Skip to content

Commit

Permalink
test(client): run functional tests in Data Proxy simulator (#15010)
Browse files Browse the repository at this point in the history
  • Loading branch information
aqrln committed Sep 9, 2022
1 parent 6fc12c0 commit a24c46c
Show file tree
Hide file tree
Showing 37 changed files with 1,694 additions and 1,319 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ packages/client/src/__tests__/types/
packages/cli/prisma-client/
packages/cli/install/
packages/cli/preinstall/
packages/cli/**/tmp-*
packages/cli/**/tmp-*
77 changes: 77 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,83 @@ jobs:
files: ./packages/client/src/__tests__/coverage/clover.xml
flags: client,${{ matrix.os }},${{ matrix.queryEngine }}
name: client-${{ matrix.os }}-${{ matrix.queryEngine }}

#
# CLIENT (functional tests with mini-proxy)
#
client-dataproxy:
timeout-minutes: 35
runs-on: ${{ matrix.os }}

needs: detect_jobs_to_run
if: ${{ contains(needs.detect_jobs_to_run.outputs.jobs, '-all-') || contains(needs.detect_jobs_to_run.outputs.jobs, '-client-') }}

strategy:
fail-fast: false
matrix:
os: [buildjet-4vcpu-ubuntu-2004]
node: [14, 16, 18]

steps:
- uses: actions/checkout@v3

- name: Login to Docker Hub
uses: docker/login-action@v2
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
if: "${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' }}"
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- run: docker-compose -f docker/docker-compose.yml up --detach postgres mysql mssql mongo cockroachdb

- uses: pnpm/action-setup@v2.2.2
with:
version: 7

- uses: actions/setup-node@v3
with:
cache: 'pnpm'
node-version: ${{ matrix.node }}

- run: bash .github/workflows/scripts/setup.sh
env:
CI: true
SKIP_GIT: true
GITHUB_CONTEXT: ${{ toJson(github) }}

- run: pnpm run test:functional --data-proxy
working-directory: packages/client
env:
CI: true
SKIP_GIT: true
GITHUB_CONTEXT: ${{ toJson(github) }}
TEST_FUNCTIONAL_POSTGRES_URI: 'postgres://prisma:prisma@localhost:5432/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MYSQL_URI: 'mysql://root:root@localhost:3306/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MSSQL_URI: 'sqlserver://localhost:1433;database=PRISMA_DB_NAME;user=SA;password=Pr1sm4_Pr1sm4;trustServerCertificate=true;'
TEST_FUNCTIONAL_MONGO_URI: 'mongodb://root:prisma@localhost:27018/PRISMA_DB_NAME?authSource=admin'
TEST_FUNCTIONAL_COCKROACH_URI: 'postgresql://prisma@localhost:26257/PRISMA_DB_NAME'

- run: pnpm run test:functional --data-proxy --edge-client
working-directory: packages/client
env:
CI: true
SKIP_GIT: true
GITHUB_CONTEXT: ${{ toJson(github) }}
TEST_FUNCTIONAL_POSTGRES_URI: 'postgres://prisma:prisma@localhost:5432/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MYSQL_URI: 'mysql://root:root@localhost:3306/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MSSQL_URI: 'sqlserver://localhost:1433;database=PRISMA_DB_NAME;user=SA;password=Pr1sm4_Pr1sm4;trustServerCertificate=true;'
TEST_FUNCTIONAL_MONGO_URI: 'mongodb://root:prisma@localhost:27018/PRISMA_DB_NAME?authSource=admin'
TEST_FUNCTIONAL_COCKROACH_URI: 'postgresql://prisma@localhost:26257/PRISMA_DB_NAME'

- uses: codecov/codecov-action@v3
with:
files: ./packages/client/src/__tests__/coverage/clover.xml
flags: client,${{ matrix.os }},dataproxy
name: client-${{ matrix.os }}-dataproxy

#
# CLIENT (memory tests)
#
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ reproductions/*

dev.db
junit.xml
/output.txt
/output.txt
12 changes: 12 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ testMatrix.setupTestSuite(
from: ['mongodb'],
reason: 'The test is for SQL databases only',
},
skipDataProxy: {
// similarly, you can opt out of testing with the Data Proxy
// client (either completely or for certain runtimes) and
// specify the reason
runtimes: ['node', 'edge'],
reason: "This test doesn't work with Data Proxy",
},
},
)
```
Expand All @@ -339,6 +346,11 @@ This test will run for every permutation of the parameters from the matrix. Curr
- `pnpm test:functional:types` runs typechecking on all the suites, generated by `pnpm test:functional:code` command. If it reports any errors, you might want to examine generated test suite under `tests/functional/<your test name>/.generated` directory to get a better diagnostic.
- `pnpm test:functional` will run tests and perform type checks.

Add `--data-proxy` to any of these commands to generate the Data Proxy client
and run the tests under the local Data Proxy simulator called Mini-Proxy.
Furthermore, you can also add `--edge-client` in addition to `--data-proxy` to
test the edge client rather than the regular Node.js client.

### Conditionally skipping type tests

Sometimes different test matrix parameters will generate different TS types so, and as a result,
Expand Down
142 changes: 115 additions & 27 deletions packages/client/helpers/functional-test/run-tests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { arg } from '@prisma/internals'
import { arg, BinaryType, getPlatform } from '@prisma/internals'
import * as miniProxy from '@prisma/mini-proxy'
import execa, { ExecaChildProcess } from 'execa'
import fs from 'fs'

import { setupQueryEngine } from '../../tests/commonUtils/setupQueryEngine'
import { Providers } from '../../tests/functional/_utils/providers'
import { JestCli } from './JestCli'

Expand All @@ -8,49 +12,133 @@ const allProviders = new Set(Object.values(Providers))
const args = arg(
process.argv.slice(2),
{
// Update snapshots
'-u': Boolean,
// Only run the tests, don't typecheck
'--no-types': Boolean,
// Only typecheck, don't run the code tests
'--types-only': Boolean,
// Restrict the list of providers
'--provider': [String],
'-p': '--provider',
// Generate Data Proxy client and run tests using Mini-Proxy
'--data-proxy': Boolean,
// Use edge client (requires --data-proxy)
'--edge-client': Boolean,
// Don't start the Mini-Proxy server, expect it to be started externally
// and listening on the default port.
'--no-mini-proxy-server': Boolean,
// Don't override NODE_EXTRA_CA_CERTS. Useful if a custom mini-proxy
// server you start is not the one in node_modules. You then need to run
// `eval $(mini-proxy env)` in your shell before starting the tests.
'--no-mini-proxy-default-ca': Boolean,
// Enable debug logs in the bundled Mini-Proxy server
'--mini-proxy-debug': Boolean,
},
true,
true,
)

let jestCli = new JestCli(['--verbose', '--config', 'tests/functional/jest.config.js'])
async function main(): Promise<number | void> {
let jestCli = new JestCli(['--verbose', '--config', 'tests/functional/jest.config.js'])
let miniProxyProcess: ExecaChildProcess | undefined

if (args['--provider']) {
const providers = args['--provider'] as Providers[]
const unknownProviders = providers.filter((provider) => !allProviders.has(provider))
if (unknownProviders.length > 0) {
console.error(`Unknown providers: ${unknownProviders.join(', ')}`)
process.exit(1)
if (args['--provider']) {
const providers = args['--provider'] as Providers[]
const unknownProviders = providers.filter((provider) => !allProviders.has(provider))
if (unknownProviders.length > 0) {
console.error(`Unknown providers: ${unknownProviders.join(', ')}`)
process.exit(1)
}
jestCli = jestCli.withEnv({ ONLY_TEST_PROVIDERS: providers.join(',') })
}
jestCli = jestCli.withEnv({ ONLY_TEST_PROVIDERS: providers.join(',') })
}

const codeTestCli = jestCli.withArgs(['--testPathIgnorePatterns', 'typescript'])
if (args['--data-proxy']) {
if (!fs.existsSync(miniProxy.defaultServerConfig.cert)) {
await miniProxy.generateCertificates(miniProxy.defaultCertificatesConfig)
}

jestCli = jestCli.withEnv({
DATA_PROXY: 'true',
})

if (!args['--no-mini-proxy-default-ca']) {
jestCli = jestCli.withEnv({
NODE_EXTRA_CA_CERTS: miniProxy.defaultCertificatesConfig.caCert,
})
}

if (args['--edge-client']) {
jestCli = jestCli.withEnv({
TEST_DATA_PROXY_EDGE_CLIENT: 'true',
})
}

if (!args['--no-mini-proxy-server']) {
const qePath = await getBinaryForMiniProxy()

try {
if (args['-u']) {
const snapshotUpdate = codeTestCli.withArgs(['-u']).withArgs(args['_'])
snapshotUpdate.withEnv({ UPDATE_SNAPSHOTS: 'inline' }).run()
snapshotUpdate.withEnv({ UPDATE_SNAPSHOTS: 'external' }).run()
} else {
if (!args['--types-only']) {
codeTestCli.withArgs(['--']).withArgs(args['_']).run()
miniProxyProcess = execa('mini-proxy', ['server', '-q', qePath], {
preferLocal: true,
stdio: 'inherit',
env: {
DEBUG: args['--mini-proxy-debug'] ? 'mini-proxy:*' : process.env.DEBUG,
},
})
}
}

if (args['--edge-client'] && !args['--data-proxy']) {
throw new Error('--edge-client is only available when --data-proxy is used')
}

if (!args['--no-types']) {
jestCli.withArgs(['--', 'typescript']).run()
const codeTestCli = jestCli.withArgs(['--testPathIgnorePatterns', 'typescript'])

try {
if (args['-u']) {
const snapshotUpdate = codeTestCli.withArgs(['-u']).withArgs(args['_'])
snapshotUpdate.withEnv({ UPDATE_SNAPSHOTS: 'inline' }).run()
snapshotUpdate.withEnv({ UPDATE_SNAPSHOTS: 'external' }).run()
} else {
if (!args['--types-only']) {
codeTestCli.withArgs(['--']).withArgs(args['_']).run()
}

if (!args['--no-types']) {
jestCli.withArgs(['--', 'typescript']).run()
}
}
} catch (error) {
if (error.exitCode) {
// If it's execa error, exit without logging: we
// already have output from jest
return error.exitCode
}
throw error
} finally {
if (miniProxyProcess) {
miniProxyProcess.kill()
}
}
}

async function getBinaryForMiniProxy(): Promise<string> {
if (process.env.PRISMA_QUERY_ENGINE_BINARY) {
return process.env.PRISMA_QUERY_ENGINE_BINARY
}
} catch (error) {
if (error.exitCode) {
// If it's execa error, exit without logging: we
// already have output from jest
process.exit(error.exitCode)

const paths = await setupQueryEngine()
const platform = await getPlatform()
const qePath = paths[BinaryType.queryEngine]?.[platform]

if (!qePath) {
throw new Error('Query Engine binary missing')
}
throw error

return qePath
}

void main().then((code) => {
if (code) {
process.exit(code)
}
})
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@prisma/instrumentation": "workspace:*",
"@prisma/internals": "workspace:*",
"@prisma/migrate": "workspace:*",
"@prisma/mini-proxy": "0.1.1",
"@swc-node/register": "1.5.1",
"@swc/core": "1.2.248",
"@swc/jest": "0.2.22",
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/utils/getTestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function getTestClient(schemaDir?: string, printWarnings?: boolean)
relativeEnvPaths,
datasourceNames: config.datasources.map((d) => d.name),
activeProvider,
dataProxy: false,
dataProxy: Boolean(process.env.DATA_PROXY),
}

return getPrismaClient(options)
Expand Down
6 changes: 3 additions & 3 deletions packages/client/tests/commonUtils/setupQueryEngine.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { enginesVersion } from '@prisma/engines'
import { download } from '@prisma/fetch-engine'
import { BinaryPaths, download } from '@prisma/fetch-engine'
import path from 'path'

/**
* Ensures the correct Query Engine (`node-api`/`binary`) is present. This is required as
* normally the downloading of the required engine is done in `getGenerators`. As the test
* clients bypass this we need to ensure the correct engine is present.
*/
export async function setupQueryEngine() {
export function setupQueryEngine(): Promise<BinaryPaths> {
const engineDownloadDir = path.resolve(__dirname, '..', '..')

await download({
return download({
binaries: {
'libquery-engine': engineDownloadDir,
'query-engine': engineDownloadDir,
Expand Down
6 changes: 3 additions & 3 deletions packages/client/tests/functional/_utils/defineMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { U } from 'ts-toolbelt'

import { TestSuiteMatrix } from './getTestSuiteInfo'
import { setupTestSuiteMatrix, TestSuiteMeta } from './setupTestSuiteMatrix'
import { MatrixOptions } from './types'
import { ClientMeta, MatrixOptions } from './types'

type MergedMatrixParams<MatrixT extends TestSuiteMatrix> = U.IntersectOf<MatrixT[number][number]>

Expand All @@ -17,15 +17,15 @@ export interface MatrixTestHelper<MatrixT extends TestSuiteMatrix> {
* and generic suite metadata as an arguments
*/
setupTestSuite(
tests: (suiteConfig: MergedMatrixParams<MatrixT>, suiteMeta: TestSuiteMeta) => void,
tests: (suiteConfig: MergedMatrixParams<MatrixT>, suiteMeta: TestSuiteMeta, clientMeta: ClientMeta) => void,
options?: MatrixOptions,
): void

/**
* Function for defining test schema. Must be used in your `prisma/_schema.ts`. Return value
* of this function should be used as a default export of that module.
*
* @param schemaCallback schema factory function. Receives all matrix paramters, used for the
* @param schemaCallback schema factory function. Receives all matrix parameters, used for the
* specific test suite at the moment.
*/
setupSchema(schemaCallback: SchemaCallback<MatrixT>): SchemaCallback<MatrixT>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function getTestSuiteMeta() {
const testRoot = path.join(testsDir, testRootDirName)
const rootRelativeTestPath = path.relative(testRoot, testPath)
const rootRelativeTestDir = path.dirname(rootRelativeTestPath)
let testName
let testName: string
if (rootRelativeTestPath === 'tests.ts') {
testName = testRootDirName
} else {
Expand Down

0 comments on commit a24c46c

Please sign in to comment.