Skip to content

Commit 2073c53

Browse files
authored
fix: Fix multiple tests on Windows and/or Temporal CLI dev server (#1104)
1 parent 9724419 commit 2073c53

File tree

13 files changed

+95
-82
lines changed

13 files changed

+95
-82
lines changed

packages/nyc-test-coverage/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class WorkflowCoverage {
172172
enforce: 'post' as const,
173173
test: /\.[tj]s$/,
174174
exclude: [
175-
/\/node_modules\//,
175+
/[/\\]node_modules[/\\]/,
176176
path.dirname(require.resolve('@temporalio/common')),
177177
path.dirname(require.resolve('@temporalio/workflow')),
178178
path.dirname(require.resolve('@temporalio/nyc-test-coverage')),

packages/test/src/helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export function cleanStackTrace(ostack: string): string {
5151
cleanedStack
5252
.replace(/:\d+:\d+/g, '')
5353
.replace(/^\s*/gms, ' at ')
54-
.replace(/\[as fn\] /, '');
54+
.replace(/\[as fn\] /, '')
55+
.replace(/\\/g, '/');
5556

5657
return normalizedStack ? `${firstLine}\n${normalizedStack}` : firstLine;
5758
}

packages/test/src/integration-tests.ts

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -562,8 +562,8 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
562562
workflowId: uuid4(),
563563
searchAttributes: {
564564
CustomKeywordField: ['test-value'],
565-
CustomIntField: [1, 2],
566-
CustomDatetimeField: [date, date],
565+
CustomIntField: [1],
566+
CustomDatetimeField: [date],
567567
},
568568
memo: {
569569
note: 'foo',
@@ -575,8 +575,8 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
575575
t.deepEqual(execution.memo, { note: 'foo' });
576576
t.true(execution.startTime instanceof Date);
577577
t.deepEqual(execution.searchAttributes!.CustomKeywordField, ['test-value']);
578-
t.deepEqual(execution.searchAttributes!.CustomIntField, [1, 2]);
579-
t.deepEqual(execution.searchAttributes!.CustomDatetimeField, [date, date]);
578+
t.deepEqual(execution.searchAttributes!.CustomIntField, [1]);
579+
t.deepEqual(execution.searchAttributes!.CustomDatetimeField, [date]);
580580
t.regex((execution.searchAttributes!.BinaryChecksums as string[])[0], /@temporalio\/worker@/);
581581
});
582582

@@ -588,15 +588,15 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
588588
workflowId: uuid4(),
589589
searchAttributes: {
590590
CustomKeywordField: ['test-value'],
591-
CustomIntField: [1, 2],
592-
CustomDatetimeField: [date, date],
591+
CustomIntField: [1],
592+
CustomDatetimeField: [date],
593593
},
594594
});
595595
const result = await workflow.result();
596596
t.deepEqual(result, {
597597
CustomKeywordField: ['test-value'],
598-
CustomIntField: [1, 2],
599-
CustomDatetimeField: [date.toISOString(), date.toISOString()],
598+
CustomIntField: [1],
599+
CustomDatetimeField: [date.toISOString()],
600600
datetimeInstanceofWorks: [true],
601601
arrayInstanceofWorks: [true],
602602
datetimeType: ['Date'],
@@ -825,7 +825,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
825825
},
826826
searchAttributes: {
827827
CustomKeywordField: ['test-value'],
828-
CustomIntField: [1, 2],
828+
CustomIntField: [1],
829829
},
830830
followRuns: true,
831831
});
@@ -836,7 +836,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
836836
t.not(execution.runId, workflow.firstExecutionRunId);
837837
t.deepEqual(execution.memo, { note: 'foo' });
838838
t.deepEqual(execution.searchAttributes!.CustomKeywordField, ['test-value']);
839-
t.deepEqual(execution.searchAttributes!.CustomIntField, [1, 2]);
839+
t.deepEqual(execution.searchAttributes!.CustomIntField, [1]);
840840
});
841841

842842
test('continue-as-new-to-different-workflow keeps memo and search attributes by default', async (t) => {
@@ -850,7 +850,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
850850
},
851851
searchAttributes: {
852852
CustomKeywordField: ['test-value'],
853-
CustomIntField: [1, 2],
853+
CustomIntField: [1],
854854
},
855855
});
856856
await workflow.result();
@@ -859,7 +859,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
859859
t.not(info.runId, workflow.firstExecutionRunId);
860860
t.deepEqual(info.memo, { note: 'foo' });
861861
t.deepEqual(info.searchAttributes!.CustomKeywordField, ['test-value']);
862-
t.deepEqual(info.searchAttributes!.CustomIntField, [1, 2]);
862+
t.deepEqual(info.searchAttributes!.CustomIntField, [1]);
863863
});
864864

865865
test('continue-as-new-to-different-workflow can set memo and search attributes', async (t) => {
@@ -873,7 +873,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
873873
},
874874
searchAttributes: {
875875
CustomKeywordField: ['test-value-2'],
876-
CustomIntField: [3, 4],
876+
CustomIntField: [3],
877877
},
878878
},
879879
],
@@ -885,7 +885,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
885885
},
886886
searchAttributes: {
887887
CustomKeywordField: ['test-value'],
888-
CustomIntField: [1, 2],
888+
CustomIntField: [1],
889889
},
890890
});
891891
await workflow.result();
@@ -894,7 +894,7 @@ export function runIntegrationTests(codec?: PayloadCodec): void {
894894
t.not(info.runId, workflow.firstExecutionRunId);
895895
t.deepEqual(info.memo, { note: 'bar' });
896896
t.deepEqual(info.searchAttributes!.CustomKeywordField, ['test-value-2']);
897-
t.deepEqual(info.searchAttributes!.CustomIntField, [3, 4]);
897+
t.deepEqual(info.searchAttributes!.CustomIntField, [3]);
898898
});
899899

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

13421344
/**
13431345
* NOTE: this test uses the `IN` operator API which requires advanced visibility as of server 1.18.
1344-
* Run with docker-compose
1346+
* It will silently succeed on servers that only support standard visibility (can't dynamically skip a test).
13451347
*/
13461348
test('Download and replay multiple executions with client list method', async (t) => {
1347-
const { metaClient: client } = t.context;
1348-
const taskQueue = 'test';
1349-
const fns = [
1350-
workflows.http,
1351-
workflows.cancelFakeProgress,
1352-
workflows.childWorkflowInvoke,
1353-
workflows.activityFailures,
1354-
];
1355-
const handles = await Promise.all(
1356-
fns.map((fn) =>
1357-
client.workflow.start(fn, {
1358-
taskQueue,
1359-
workflowId: uuid4(),
1360-
})
1361-
)
1362-
);
1363-
// Wait for the workflows to complete first
1364-
await Promise.all(handles.map((h) => h.result()));
1365-
// Test the list API too while we're at it
1366-
const workflowIds = handles.map(({ workflowId }) => `'${workflowId}'`);
1367-
const histories = client.workflow.list({ query: `WorkflowId IN (${workflowIds.join(', ')})` }).intoHistories();
1368-
const results = Worker.runReplayHistories(
1369-
{
1370-
workflowsPath: require.resolve('./workflows'),
1371-
dataConverter: t.context.dataConverter,
1372-
},
1373-
histories
1374-
);
1349+
try {
1350+
const { metaClient: client } = t.context;
1351+
const taskQueue = 'test';
1352+
const fns = [
1353+
workflows.http,
1354+
workflows.cancelFakeProgress,
1355+
workflows.childWorkflowInvoke,
1356+
workflows.activityFailures,
1357+
];
1358+
const handles = await Promise.all(
1359+
fns.map((fn) =>
1360+
client.workflow.start(fn, {
1361+
taskQueue,
1362+
workflowId: uuid4(),
1363+
})
1364+
)
1365+
);
1366+
// Wait for the workflows to complete first
1367+
await Promise.all(handles.map((h) => h.result()));
1368+
// Test the list API too while we're at it
1369+
const workflowIds = handles.map(({ workflowId }) => `'${workflowId}'`);
1370+
const histories = client.workflow.list({ query: `WorkflowId IN (${workflowIds.join(', ')})` }).intoHistories();
1371+
const results = await Worker.runReplayHistories(
1372+
{
1373+
workflowsPath: require.resolve('./workflows'),
1374+
dataConverter: t.context.dataConverter,
1375+
},
1376+
histories
1377+
);
13751378

1376-
for await (const result of results) {
1377-
t.is(result.error, undefined);
1379+
for await (const result of results) {
1380+
t.is(result.error, undefined);
1381+
}
1382+
} catch (e) {
1383+
// Don't report a test failure if the server does not support extended query
1384+
if (!(e as Error).message?.includes(`operator 'in' not allowed`)) throw e;
13781385
}
13791386
t.pass();
13801387
});

packages/test/src/test-native-connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if (RUN_INTEGRATION_TESTS) {
2121
test('NativeConnection errors have detail', async (t) => {
2222
await t.throwsAsync(() => NativeConnection.connect({ address: 'localhost:1' }), {
2323
instanceOf: TransportError,
24-
message: /.*Connection refused.*/,
24+
message: /.*Connection[ ]?refused.*/i,
2525
});
2626
});
2727

packages/test/src/test-nyc-coverage.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import test from 'ava';
22
import { v4 as uuid4 } from 'uuid';
33
import * as libCoverage from 'istanbul-lib-coverage';
44
import { bundleWorkflowCode, Worker } from '@temporalio/worker';
5-
import { WorkflowClient } from '@temporalio/client';
5+
import { Client, WorkflowClient } from '@temporalio/client';
66
import { WorkflowCoverage } from '@temporalio/nyc-test-coverage';
77
import { RUN_INTEGRATION_TESTS } from './helpers';
88
import { successString } from './workflows';
@@ -26,13 +26,13 @@ if (RUN_INTEGRATION_TESTS) {
2626
workflowsPath: require.resolve('./workflows'),
2727
})
2828
);
29-
const client = new WorkflowClient();
30-
await worker.runUntil(client.execute(successString, { taskQueue, workflowId: uuid4() }));
29+
const client = new Client();
30+
await worker.runUntil(client.workflow.execute(successString, { taskQueue, workflowId: uuid4() }));
3131

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

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

6363
workflowCoverageBundler.mergeIntoGlobalCoverage();
6464
workflowCoverageWorker.mergeIntoGlobalCoverage();
6565
const coverageMap = libCoverage.createCoverageMap(global.__coverage__);
6666
console.log(coverageMap.files());
6767

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

9393
// Only user code should be included in coverage
94-
t.is(coverageMap.files().filter((x) => x.match(/\/worker-interface.js/)).length, 0);
95-
t.is(coverageMap.files().filter((x) => x.match(/\/ms\//)).length, 0);
94+
t.is(coverageMap.files().filter((x) => x.match(/[/\\]worker-interface.js/)).length, 0);
95+
t.is(coverageMap.files().filter((x) => x.match(/[/\\]ms[/\\]/)).length, 0);
9696
});
9797
}

packages/test/src/test-schedules.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { randomUUID } from 'node:crypto';
22
import anyTest, { TestFn } from 'ava';
33
import asyncRetry from 'async-retry';
44
import {
5-
Client,
65
defaultPayloadConverter,
76
CalendarSpec,
87
CalendarSpecDescription,
8+
Client,
99
Connection,
1010
ScheduleHandle,
1111
ScheduleSummary,
@@ -218,7 +218,7 @@ if (RUN_INTEGRATION_TESTS) {
218218
}
219219
});
220220

221-
test('Interceptor is called on create schedule', async (t) => {
221+
test.serial('Interceptor is called on create schedule', async (t) => {
222222
const clientWithInterceptor = new Client({
223223
connection: t.context.client.connection,
224224
interceptors: {

packages/testing/src/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ export async function waitOnNamespace(
1515
execution: { workflowId: 'fake', runId },
1616
});
1717
} catch (err: any) {
18-
if (err.details.includes('workflow history not found') || err.details.includes(runId)) {
18+
if (
19+
err.details.includes('workflow history not found') ||
20+
err.details.includes('Workflow executionsRow not found') ||
21+
err.details.includes(runId)
22+
) {
1923
break;
2024
}
2125
if (attempt === maxAttempts) {

packages/workflow/src/workflow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ export function setDefaultSignalHandler(handler: DefaultSignalHandler | undefine
12101210
*
12111211
* ```ts
12121212
* upsertSearchAttributes({
1213-
* CustomIntField: [1, 2, 3],
1213+
* CustomIntField: [1],
12141214
* CustomBoolField: [true]
12151215
* });
12161216
* upsertSearchAttributes({

scripts/create-certs-dir.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Used in CI flow to store the Cloud certs from GH secret into local files for testing the mTLS sample.
33
const fs = require('fs-extra');
44

5-
fs.mkdirsSync('/tmp/temporal-certs');
6-
fs.writeFileSync('/tmp/temporal-certs/client.pem', process.env.TEMPORAL_CLIENT_CERT);
7-
fs.writeFileSync('/tmp/temporal-certs/client.key', process.env.TEMPORAL_CLIENT_KEY);
5+
const targetDir = process.argv[2] ?? '/tmp/temporal-certs';
6+
7+
fs.mkdirsSync(targetDir);
8+
fs.writeFileSync(`${targetDir}/client.pem`, process.env.TEMPORAL_CLIENT_CERT);
9+
fs.writeFileSync(`${targetDir}/client.key`, process.env.TEMPORAL_CLIENT_KEY);

scripts/init-from-verdaccio.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const { resolve } = require('path');
1+
const { resolve, dirname } = require('path');
22
const { writeFileSync } = require('fs');
33
const { withRegistry, getArgs } = require('./registry');
44
const { spawnNpx } = require('./utils');
55

66
async function main() {
7-
const { registryDir, initArgs } = await getArgs();
7+
const { registryDir, targetDir, initArgs } = await getArgs();
88

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

1616
await spawnNpx(
17-
['@temporalio/create', 'example', '--no-git-init', '--temporalio-version', 'latest', ...initArgs],
17+
['@temporalio/create', targetDir, '--no-git-init', '--temporalio-version', 'latest', ...initArgs],
1818
{
1919
stdio: 'inherit',
2020
stdout: 'inherit',
2121
stderr: 'inherit',
22-
cwd: registryDir,
22+
cwd: dirname(targetDir),
2323
env: {
2424
...process.env,
2525
NPM_CONFIG_USERCONFIG: npmConfigFile,

scripts/registry.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,13 @@ async function getArgs() {
8383
const opts = arg(
8484
{
8585
'--registry-dir': String,
86+
'--target-dir': String,
8687
},
8788
{ permissive: true }
8889
);
89-
const registryDir = opts['--registry-dir'] || (await createTempRegistryDir());
90-
return { registryDir, initArgs: opts._.length > 0 ? opts._ : [] };
90+
const registryDir = opts['--registry-dir'] ?? (await createTempRegistryDir());
91+
const targetDir = opts['--target-dir'] ?? path.join(registryDir, 'example');
92+
return { registryDir, targetDir, initArgs: opts._.length > 0 ? opts._ : [] };
9193
}
9294

9395
module.exports = { getArgs, withRegistry };

scripts/utils.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,12 @@ async function kill(child, signal = 'SIGINT') {
4242
}
4343

4444
async function spawnNpx(args, opts) {
45-
let fullCommand = ['npx', '--prefer-offline', '--timing=true', '--yes', '--', ...args];
46-
47-
// NPM is a .cmd on Windows
48-
if (process.platform == 'win32') {
49-
fullCommand = ['cmd', '/C', ...fullCommand];
50-
}
51-
52-
await waitOnChild(spawn(fullCommand[0], fullCommand.slice(1), opts));
45+
const npx = /^win/.test(process.platform) ? 'npx.cmd' : 'npx';
46+
const npxArgs = ['--prefer-offline', '--timing=true', '--yes', '--', ...args];
47+
await waitOnChild(spawn(npx, npxArgs, opts));
5348
}
5449

55-
const shell = process.platform === 'win32';
50+
const shell = /^win/.test(process.platform);
5651
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5752

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

0 commit comments

Comments
 (0)