Skip to content

Commit

Permalink
fix: Fix multiple tests on Windows and/or Temporal CLI dev server (#1104
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mjameswh authored Apr 20, 2023
1 parent 9724419 commit 2073c53
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 82 deletions.
2 changes: 1 addition & 1 deletion packages/nyc-test-coverage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export class WorkflowCoverage {
enforce: 'post' as const,
test: /\.[tj]s$/,
exclude: [
/\/node_modules\//,
/[/\\]node_modules[/\\]/,
path.dirname(require.resolve('@temporalio/common')),
path.dirname(require.resolve('@temporalio/workflow')),
path.dirname(require.resolve('@temporalio/nyc-test-coverage')),
Expand Down
3 changes: 2 additions & 1 deletion packages/test/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export function cleanStackTrace(ostack: string): string {
cleanedStack
.replace(/:\d+:\d+/g, '')
.replace(/^\s*/gms, ' at ')
.replace(/\[as fn\] /, '');
.replace(/\[as fn\] /, '')
.replace(/\\/g, '/');

return normalizedStack ? `${firstLine}\n${normalizedStack}` : firstLine;
}
Expand Down
101 changes: 54 additions & 47 deletions packages/test/src/integration-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,8 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
workflowId: uuid4(),
searchAttributes: {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomDatetimeField: [date, date],
CustomIntField: [1],
CustomDatetimeField: [date],
},
memo: {
note: 'foo',
Expand All @@ -575,8 +575,8 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
t.deepEqual(execution.memo, { note: 'foo' });
t.true(execution.startTime instanceof Date);
t.deepEqual(execution.searchAttributes!.CustomKeywordField, ['test-value']);
t.deepEqual(execution.searchAttributes!.CustomIntField, [1, 2]);
t.deepEqual(execution.searchAttributes!.CustomDatetimeField, [date, date]);
t.deepEqual(execution.searchAttributes!.CustomIntField, [1]);
t.deepEqual(execution.searchAttributes!.CustomDatetimeField, [date]);
t.regex((execution.searchAttributes!.BinaryChecksums as string[])[0], /@temporalio\/worker@/);
});

Expand All @@ -588,15 +588,15 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
workflowId: uuid4(),
searchAttributes: {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomDatetimeField: [date, date],
CustomIntField: [1],
CustomDatetimeField: [date],
},
});
const result = await workflow.result();
t.deepEqual(result, {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomDatetimeField: [date.toISOString(), date.toISOString()],
CustomIntField: [1],
CustomDatetimeField: [date.toISOString()],
datetimeInstanceofWorks: [true],
arrayInstanceofWorks: [true],
datetimeType: ['Date'],
Expand Down Expand Up @@ -825,7 +825,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
},
searchAttributes: {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomIntField: [1],
},
followRuns: true,
});
Expand All @@ -836,7 +836,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
t.not(execution.runId, workflow.firstExecutionRunId);
t.deepEqual(execution.memo, { note: 'foo' });
t.deepEqual(execution.searchAttributes!.CustomKeywordField, ['test-value']);
t.deepEqual(execution.searchAttributes!.CustomIntField, [1, 2]);
t.deepEqual(execution.searchAttributes!.CustomIntField, [1]);
});

test('continue-as-new-to-different-workflow keeps memo and search attributes by default', async (t) => {
Expand All @@ -850,7 +850,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
},
searchAttributes: {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomIntField: [1],
},
});
await workflow.result();
Expand All @@ -859,7 +859,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
t.not(info.runId, workflow.firstExecutionRunId);
t.deepEqual(info.memo, { note: 'foo' });
t.deepEqual(info.searchAttributes!.CustomKeywordField, ['test-value']);
t.deepEqual(info.searchAttributes!.CustomIntField, [1, 2]);
t.deepEqual(info.searchAttributes!.CustomIntField, [1]);
});

test('continue-as-new-to-different-workflow can set memo and search attributes', async (t) => {
Expand All @@ -873,7 +873,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
},
searchAttributes: {
CustomKeywordField: ['test-value-2'],
CustomIntField: [3, 4],
CustomIntField: [3],
},
},
],
Expand All @@ -885,7 +885,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
},
searchAttributes: {
CustomKeywordField: ['test-value'],
CustomIntField: [1, 2],
CustomIntField: [1],
},
});
await workflow.result();
Expand All @@ -894,7 +894,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
t.not(info.runId, workflow.firstExecutionRunId);
t.deepEqual(info.memo, { note: 'bar' });
t.deepEqual(info.searchAttributes!.CustomKeywordField, ['test-value-2']);
t.deepEqual(info.searchAttributes!.CustomIntField, [3, 4]);
t.deepEqual(info.searchAttributes!.CustomIntField, [3]);
});

test('signalWithStart works as intended and returns correct runId', async (t) => {
Expand Down Expand Up @@ -1259,7 +1259,9 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
const stacks = enhancedStack.stacks.map((s) => ({
locations: s.locations.map((l) => ({
...l,
...(l.filePath ? { filePath: l.filePath.replace(path.resolve(__dirname, '../../../'), '') } : undefined),
...(l.filePath
? { filePath: l.filePath.replace(path.resolve(__dirname, '../../../'), '').replace(/\\/g, '/') }
: undefined),
})),
}));
t.is(enhancedStack.sdk.name, 'typescript');
Expand Down Expand Up @@ -1341,40 +1343,45 @@ export function runIntegrationTests(codec?: PayloadCodec): void {

/**
* NOTE: this test uses the `IN` operator API which requires advanced visibility as of server 1.18.
* Run with docker-compose
* It will silently succeed on servers that only support standard visibility (can't dynamically skip a test).
*/
test('Download and replay multiple executions with client list method', async (t) => {
const { metaClient: client } = t.context;
const taskQueue = 'test';
const fns = [
workflows.http,
workflows.cancelFakeProgress,
workflows.childWorkflowInvoke,
workflows.activityFailures,
];
const handles = await Promise.all(
fns.map((fn) =>
client.workflow.start(fn, {
taskQueue,
workflowId: uuid4(),
})
)
);
// Wait for the workflows to complete first
await Promise.all(handles.map((h) => h.result()));
// Test the list API too while we're at it
const workflowIds = handles.map(({ workflowId }) => `'${workflowId}'`);
const histories = client.workflow.list({ query: `WorkflowId IN (${workflowIds.join(', ')})` }).intoHistories();
const results = Worker.runReplayHistories(
{
workflowsPath: require.resolve('./workflows'),
dataConverter: t.context.dataConverter,
},
histories
);
try {
const { metaClient: client } = t.context;
const taskQueue = 'test';
const fns = [
workflows.http,
workflows.cancelFakeProgress,
workflows.childWorkflowInvoke,
workflows.activityFailures,
];
const handles = await Promise.all(
fns.map((fn) =>
client.workflow.start(fn, {
taskQueue,
workflowId: uuid4(),
})
)
);
// Wait for the workflows to complete first
await Promise.all(handles.map((h) => h.result()));
// Test the list API too while we're at it
const workflowIds = handles.map(({ workflowId }) => `'${workflowId}'`);
const histories = client.workflow.list({ query: `WorkflowId IN (${workflowIds.join(', ')})` }).intoHistories();
const results = await Worker.runReplayHistories(
{
workflowsPath: require.resolve('./workflows'),
dataConverter: t.context.dataConverter,
},
histories
);

for await (const result of results) {
t.is(result.error, undefined);
for await (const result of results) {
t.is(result.error, undefined);
}
} catch (e) {
// Don't report a test failure if the server does not support extended query
if (!(e as Error).message?.includes(`operator 'in' not allowed`)) throw e;
}
t.pass();
});
Expand Down
2 changes: 1 addition & 1 deletion packages/test/src/test-native-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if (RUN_INTEGRATION_TESTS) {
test('NativeConnection errors have detail', async (t) => {
await t.throwsAsync(() => NativeConnection.connect({ address: 'localhost:1' }), {
instanceOf: TransportError,
message: /.*Connection refused.*/,
message: /.*Connection[ ]?refused.*/i,
});
});

Expand Down
18 changes: 9 additions & 9 deletions packages/test/src/test-nyc-coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import test from 'ava';
import { v4 as uuid4 } from 'uuid';
import * as libCoverage from 'istanbul-lib-coverage';
import { bundleWorkflowCode, Worker } from '@temporalio/worker';
import { WorkflowClient } from '@temporalio/client';
import { Client, WorkflowClient } from '@temporalio/client';
import { WorkflowCoverage } from '@temporalio/nyc-test-coverage';
import { RUN_INTEGRATION_TESTS } from './helpers';
import { successString } from './workflows';
Expand All @@ -26,13 +26,13 @@ if (RUN_INTEGRATION_TESTS) {
workflowsPath: require.resolve('./workflows'),
})
);
const client = new WorkflowClient();
await worker.runUntil(client.execute(successString, { taskQueue, workflowId: uuid4() }));
const client = new Client();
await worker.runUntil(client.workflow.execute(successString, { taskQueue, workflowId: uuid4() }));

workflowCoverage.mergeIntoGlobalCoverage();
const coverageMap = libCoverage.createCoverageMap(global.__coverage__);

const successStringFileName = coverageMap.files().find((x) => x.match(/\/success-string\.js/));
const successStringFileName = coverageMap.files().find((x) => x.match(/[/\\]success-string\.js/));
if (successStringFileName) {
t.is(coverageMap.fileCoverageFor(successStringFileName).toSummary().lines.pct, 100);
} else t.fail();
Expand All @@ -57,15 +57,15 @@ if (RUN_INTEGRATION_TESTS) {
workflowBundle: { code },
})
);
const client = new WorkflowClient();
await worker.runUntil(client.execute(successString, { taskQueue, workflowId: uuid4() }));
const client = new Client();
await worker.runUntil(client.workflow.execute(successString, { taskQueue, workflowId: uuid4() }));

workflowCoverageBundler.mergeIntoGlobalCoverage();
workflowCoverageWorker.mergeIntoGlobalCoverage();
const coverageMap = libCoverage.createCoverageMap(global.__coverage__);
console.log(coverageMap.files());

const successStringFileName = coverageMap.files().find((x) => x.match(/\/success-string\.js/));
const successStringFileName = coverageMap.files().find((x) => x.match(/[/\\]success-string\.js/));
if (successStringFileName) {
t.is(coverageMap.fileCoverageFor(successStringFileName).toSummary().lines.pct, 100);
} else t.fail();
Expand All @@ -91,7 +91,7 @@ if (RUN_INTEGRATION_TESTS) {
const coverageMap = libCoverage.createCoverageMap(global.__coverage__);

// Only user code should be included in coverage
t.is(coverageMap.files().filter((x) => x.match(/\/worker-interface.js/)).length, 0);
t.is(coverageMap.files().filter((x) => x.match(/\/ms\//)).length, 0);
t.is(coverageMap.files().filter((x) => x.match(/[/\\]worker-interface.js/)).length, 0);
t.is(coverageMap.files().filter((x) => x.match(/[/\\]ms[/\\]/)).length, 0);
});
}
4 changes: 2 additions & 2 deletions packages/test/src/test-schedules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { randomUUID } from 'node:crypto';
import anyTest, { TestFn } from 'ava';
import asyncRetry from 'async-retry';
import {
Client,
defaultPayloadConverter,
CalendarSpec,
CalendarSpecDescription,
Client,
Connection,
ScheduleHandle,
ScheduleSummary,
Expand Down Expand Up @@ -218,7 +218,7 @@ if (RUN_INTEGRATION_TESTS) {
}
});

test('Interceptor is called on create schedule', async (t) => {
test.serial('Interceptor is called on create schedule', async (t) => {
const clientWithInterceptor = new Client({
connection: t.context.client.connection,
interceptors: {
Expand Down
6 changes: 5 additions & 1 deletion packages/testing/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export async function waitOnNamespace(
execution: { workflowId: 'fake', runId },
});
} catch (err: any) {
if (err.details.includes('workflow history not found') || err.details.includes(runId)) {
if (
err.details.includes('workflow history not found') ||
err.details.includes('Workflow executionsRow not found') ||
err.details.includes(runId)
) {
break;
}
if (attempt === maxAttempts) {
Expand Down
2 changes: 1 addition & 1 deletion packages/workflow/src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ export function setDefaultSignalHandler(handler: DefaultSignalHandler | undefine
*
* ```ts
* upsertSearchAttributes({
* CustomIntField: [1, 2, 3],
* CustomIntField: [1],
* CustomBoolField: [true]
* });
* upsertSearchAttributes({
Expand Down
8 changes: 5 additions & 3 deletions scripts/create-certs-dir.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Used in CI flow to store the Cloud certs from GH secret into local files for testing the mTLS sample.
const fs = require('fs-extra');

fs.mkdirsSync('/tmp/temporal-certs');
fs.writeFileSync('/tmp/temporal-certs/client.pem', process.env.TEMPORAL_CLIENT_CERT);
fs.writeFileSync('/tmp/temporal-certs/client.key', process.env.TEMPORAL_CLIENT_KEY);
const targetDir = process.argv[2] ?? '/tmp/temporal-certs';

fs.mkdirsSync(targetDir);
fs.writeFileSync(`${targetDir}/client.pem`, process.env.TEMPORAL_CLIENT_CERT);
fs.writeFileSync(`${targetDir}/client.key`, process.env.TEMPORAL_CLIENT_KEY);
8 changes: 4 additions & 4 deletions scripts/init-from-verdaccio.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const { resolve } = require('path');
const { resolve, dirname } = require('path');
const { writeFileSync } = require('fs');
const { withRegistry, getArgs } = require('./registry');
const { spawnNpx } = require('./utils');

async function main() {
const { registryDir, initArgs } = await getArgs();
const { registryDir, targetDir, initArgs } = await getArgs();

await withRegistry(registryDir, async () => {
console.log('spawning npx @temporalio/create with args:', initArgs);
Expand All @@ -14,12 +14,12 @@ async function main() {
writeFileSync(npmConfigFile, npmConfig, { encoding: 'utf-8' });

await spawnNpx(
['@temporalio/create', 'example', '--no-git-init', '--temporalio-version', 'latest', ...initArgs],
['@temporalio/create', targetDir, '--no-git-init', '--temporalio-version', 'latest', ...initArgs],
{
stdio: 'inherit',
stdout: 'inherit',
stderr: 'inherit',
cwd: registryDir,
cwd: dirname(targetDir),
env: {
...process.env,
NPM_CONFIG_USERCONFIG: npmConfigFile,
Expand Down
6 changes: 4 additions & 2 deletions scripts/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,13 @@ async function getArgs() {
const opts = arg(
{
'--registry-dir': String,
'--target-dir': String,
},
{ permissive: true }
);
const registryDir = opts['--registry-dir'] || (await createTempRegistryDir());
return { registryDir, initArgs: opts._.length > 0 ? opts._ : [] };
const registryDir = opts['--registry-dir'] ?? (await createTempRegistryDir());
const targetDir = opts['--target-dir'] ?? path.join(registryDir, 'example');
return { registryDir, targetDir, initArgs: opts._.length > 0 ? opts._ : [] };
}

module.exports = { getArgs, withRegistry };
13 changes: 4 additions & 9 deletions scripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,12 @@ async function kill(child, signal = 'SIGINT') {
}

async function spawnNpx(args, opts) {
let fullCommand = ['npx', '--prefer-offline', '--timing=true', '--yes', '--', ...args];

// NPM is a .cmd on Windows
if (process.platform == 'win32') {
fullCommand = ['cmd', '/C', ...fullCommand];
}

await waitOnChild(spawn(fullCommand[0], fullCommand.slice(1), opts));
const npx = /^win/.test(process.platform) ? 'npx.cmd' : 'npx';
const npxArgs = ['--prefer-offline', '--timing=true', '--yes', '--', ...args];
await waitOnChild(spawn(npx, npxArgs, opts));
}

const shell = process.platform === 'win32';
const shell = /^win/.test(process.platform);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

module.exports = { kill, spawnNpx, ChildProcessError, shell, sleep };
Loading

0 comments on commit 2073c53

Please sign in to comment.