Skip to content

Commit 305c823

Browse files
committed
Plug internal stores to async-queue plugin & test
1 parent d5e236e commit 305c823

6 files changed

Lines changed: 233 additions & 30 deletions

File tree

packages/core/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ export type GetPluginsArg = {
212212
bundler: any;
213213
context: GlobalContext;
214214
options: Options;
215+
data: GlobalData;
216+
stores: GlobalStores;
215217
};
216218
export type GetPlugins = (arg: GetPluginsArg) => PluginOptions[];
217219
export type GetCustomPlugins = (arg: GetPluginsArg) => CustomPluginOptions[];

packages/factory/src/helpers/logger.test.ts

Lines changed: 197 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,48 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2019-Present Datadog, Inc.
44

5-
import type { GlobalData, GlobalStores, Logger } from '@dd/core/types';
5+
import { datadogEsbuildPlugin } from '@datadog/esbuild-plugin';
6+
import { datadogRollupPlugin } from '@datadog/rollup-plugin';
7+
import { datadogRspackPlugin } from '@datadog/rspack-plugin';
8+
import { datadogVitePlugin } from '@datadog/vite-plugin';
9+
import { rm } from '@dd/core/helpers/fs';
10+
import { getSendLog } from '@dd/core/helpers/log';
11+
import { getUniqueId } from '@dd/core/helpers/strings';
12+
import type {
13+
BundlerFullName,
14+
GetPluginsArg,
15+
GlobalData,
16+
GlobalStores,
17+
Logger,
18+
Options,
19+
} from '@dd/core/types';
620
import { getLoggerFactory, NAME_SEP } from '@dd/factory/helpers/logger';
7-
import { getMockData, getMockStores } from '@dd/tests/_jest/helpers/mocks';
21+
import { getAsyncQueuePlugins } from '@dd/internal-async-queue-plugin';
22+
import { prepareWorkingDir } from '@dd/tests/_jest/helpers/env';
23+
import { getWebpackPlugin } from '@dd/tests/_jest/helpers/getWebpackPlugin';
24+
import {
25+
defaultEntry,
26+
defaultPluginOptions,
27+
getMockData,
28+
getMockStores,
29+
} from '@dd/tests/_jest/helpers/mocks';
30+
import { BUNDLERS } from '@dd/tests/_jest/helpers/runBundlers';
31+
import { allBundlers } from '@dd/tools/bundlers';
32+
// import { allPlugins } from '@dd/tools/plugins';
33+
import path from 'path';
834
import stripAnsi from 'strip-ansi';
35+
import webpack4 from 'webpack4';
36+
import webpack5 from 'webpack5';
937

1038
// Keep a reference to console.log for debugging.
11-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1239
const log = console.log;
40+
const error = console.error;
41+
const warn = console.warn;
1342

1443
// Spy on console to avoid logs in the console and to assert.
15-
jest.spyOn(console, 'log').mockImplementation(() => {});
16-
jest.spyOn(console, 'error').mockImplementation(() => {});
17-
jest.spyOn(console, 'warn').mockImplementation(() => {});
44+
jest.spyOn(console, 'log').mockImplementation(log);
45+
jest.spyOn(console, 'error').mockImplementation(error);
46+
jest.spyOn(console, 'warn').mockImplementation(warn);
1847

1948
const logMock = jest.mocked(console.log);
2049
const errorMock = jest.mocked(console.error);
@@ -23,6 +52,31 @@ const warnMock = jest.mocked(console.warn);
2352
// Access logs and strip colors.
2453
const getOutput = (mock: jest.Mock, index: number) => stripAnsi(mock.mock.calls[index][0]);
2554

55+
// Mock getSendLog for testing forward option
56+
jest.mock('@dd/core/helpers/log', () => ({
57+
getSendLog: jest.fn(() => jest.fn().mockResolvedValue(undefined)),
58+
}));
59+
// Mock the getLogger function from the context.
60+
jest.mock('@dd/factory/helpers/logger', () => {
61+
const originalModule = jest.requireActual('@dd/factory/helpers/logger');
62+
return {
63+
...originalModule,
64+
getLoggerFactory: jest.fn(originalModule.getLoggerFactory),
65+
};
66+
});
67+
68+
jest.mock('@dd/internal-async-queue-plugin', () => {
69+
const originalModule = jest.requireActual('@dd/internal-async-queue-plugin');
70+
return {
71+
...originalModule,
72+
getAsyncQueuePlugins: jest.fn(originalModule.getAsyncQueuePlugins),
73+
};
74+
});
75+
76+
const mockGetSendLog = jest.mocked(getSendLog);
77+
const mockGetLoggerFactory = jest.mocked(getLoggerFactory);
78+
const mockGetAsyncQueuePlugins = jest.mocked(getAsyncQueuePlugins);
79+
2680
describe('logger', () => {
2781
describe('getLoggerFactory', () => {
2882
const setupLogger = (name: string): [Logger, GlobalStores, GlobalData] => {
@@ -272,5 +326,142 @@ describe('logger', () => {
272326
assessStores(`testLogger${NAME_SEP}subLogger`, stores);
273327
});
274328
});
329+
330+
describe('Forward option', () => {
331+
test('Should add promises to queue when forward option is used', () => {
332+
const [logger, stores] = setupLogger('testLogger');
333+
const mockSendLogFn = jest.fn().mockResolvedValue(undefined);
334+
mockGetSendLog.mockReturnValue(mockSendLogFn);
335+
336+
// Log with forward option
337+
logger.info('Test forwarded log', { forward: true });
338+
339+
// Check that sendLog was called
340+
expect(mockSendLogFn).toHaveBeenCalledTimes(1);
341+
342+
// Check that promises were added to the queue
343+
expect(stores.queue).toHaveLength(1);
344+
345+
// Check that sendLog function was called with correct parameters
346+
expect(mockSendLogFn).toHaveBeenCalledWith({
347+
message: 'Test forwarded log',
348+
context: { plugin: 'testLogger', status: 'info' },
349+
});
350+
});
351+
352+
test('Should not add to queue when forward option is not used', () => {
353+
const [logger, stores] = setupLogger('testLogger');
354+
355+
// Log without forward option
356+
logger.info('Test log');
357+
logger.error('Test error');
358+
359+
// Check that queue is empty
360+
expect(stores.queue).toHaveLength(0);
361+
});
362+
363+
describe('Full build', () => {
364+
let stores: GlobalStores;
365+
let logger: Logger;
366+
const promiseResolves: (() => void)[] = [];
367+
const mockAsyncCall = jest.fn();
368+
const outDirsToRm: string[] = [];
369+
const buildPromises: Promise<any>[] = [];
370+
371+
beforeAll(async () => {
372+
// Mocks.
373+
[logger, stores] = setupLogger('testLogger');
374+
375+
// Use an async function, manually resolved from outside.
376+
const mockSendLogFn = jest.fn().mockImplementation(() => {
377+
return new Promise((resolve) => {
378+
promiseResolves.push(() => {
379+
mockAsyncCall();
380+
resolve(undefined);
381+
});
382+
});
383+
});
384+
mockGetSendLog.mockReturnValue(mockSendLogFn);
385+
mockGetLoggerFactory.mockReturnValue(() => logger);
386+
387+
// Mock the async queue plugins to use our mock stores.
388+
mockGetAsyncQueuePlugins.mockImplementation((args: GetPluginsArg) => {
389+
const original: typeof getAsyncQueuePlugins = jest.requireActual(
390+
'@dd/internal-async-queue-plugin',
391+
).getAsyncQueuePlugins;
392+
args.stores = stores;
393+
return original(args);
394+
});
395+
396+
const pluginConfig: Options = {
397+
...defaultPluginOptions,
398+
customPlugins: ({ context }) => {
399+
context
400+
.getLogger('testLogger')
401+
.info('Test forwarded log', { forward: true });
402+
return [];
403+
},
404+
};
405+
406+
// Prepare the working directory, where we'll output our builds.
407+
const seed: string = `${Math.abs(jest.getSeed())}.${getUniqueId()}`;
408+
const workingDir = await prepareWorkingDir(seed);
409+
outDirsToRm.push(workingDir);
410+
411+
// Using these plugins to target the dev files,
412+
// so the mocks are correctly injected.
413+
const allPlugins: Record<BundlerFullName, (config: Options) => any> = {
414+
webpack4: (config) => getWebpackPlugin(config, webpack4),
415+
webpack5: (config) => getWebpackPlugin(config, webpack5),
416+
rspack: (config) => datadogRspackPlugin(config),
417+
esbuild: (config) => datadogEsbuildPlugin(config),
418+
rollup: (config) => datadogRollupPlugin(config),
419+
vite: (config) => datadogVitePlugin(config),
420+
};
421+
422+
// Build with all the bundlers.
423+
for (const bundler of BUNDLERS) {
424+
const getPlugin = allPlugins[bundler.name];
425+
const { run, config } = allBundlers[bundler.name];
426+
// Store the promises without awaiting them.
427+
buildPromises.push(
428+
run(
429+
config({
430+
workingDir,
431+
entry: { main: path.resolve(workingDir, defaultEntry) },
432+
outDir: path.join(workingDir, bundler.name),
433+
plugins: [getPlugin(pluginConfig)],
434+
}),
435+
),
436+
);
437+
}
438+
});
439+
440+
afterAll(async () => {
441+
if (process.env.NO_CLEANUP) {
442+
return;
443+
}
444+
try {
445+
await Promise.all(outDirsToRm.map((dir) => rm(dir)));
446+
} catch (e) {
447+
// Ignore errors.
448+
}
449+
});
450+
451+
test('Should handle async queue processing', async () => {
452+
// Verify promise was added to queue
453+
expect(stores.queue).toHaveLength(BUNDLERS.length);
454+
// We should not have called the async function yet.
455+
expect(mockAsyncCall).not.toHaveBeenCalled();
456+
457+
// Resolve the awaiting promises, so the builds can complete.
458+
promiseResolves.forEach((resolve) => resolve());
459+
await Promise.all(buildPromises);
460+
461+
// Verify the promises were resolved
462+
expect(mockAsyncCall).toHaveBeenCalledTimes(BUNDLERS.length);
463+
});
464+
});
465+
});
275466
});
276467
});

packages/factory/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export const buildPluginFactory = ({
167167
bundler,
168168
context,
169169
options,
170+
data,
171+
stores,
170172
}),
171173
);
172174
}

packages/plugins/async-queue/src/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import { PLUGIN_NAME } from './constants';
99
export { PLUGIN_NAME };
1010

1111
export const getAsyncQueuePlugins: GetInternalPlugins = (arg: GetPluginsArg) => {
12-
const { context } = arg;
12+
const { context, stores } = arg;
1313
const log = context.getLogger(PLUGIN_NAME);
14-
const promises: Promise<any>[] = [];
1514
const errors: string[] = [];
1615

1716
// Initialize the queue function
@@ -20,16 +19,15 @@ export const getAsyncQueuePlugins: GetInternalPlugins = (arg: GetPluginsArg) =>
2019
const wrappedPromise = promise.catch((error: any) => {
2120
errors.push(error.message || error.toString());
2221
});
23-
promises.push(wrappedPromise);
22+
stores.queue.push(wrappedPromise);
2423
};
2524

2625
return [
2726
{
2827
name: PLUGIN_NAME,
2928
asyncTrueEnd: async () => {
3029
// Await for all promises to finish processing.
31-
await Promise.all(promises);
32-
30+
await Promise.all(stores.queue);
3331
if (errors.length > 0) {
3432
log.error(
3533
`Error occurred while processing async queue:\n ${errors.join('\n ')}`,

packages/tests/src/_jest/helpers/mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ export const getGetPluginsArg = (
205205
return {
206206
options: optionsOverrides,
207207
context: getContextMock(contextOverrides),
208+
data: getMockData(),
209+
stores: getMockStores(),
208210
bundler: {},
209211
};
210212
};

packages/tools/src/helpers.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
GetPluginsArg,
1111
GetPlugins,
1212
OptionsWithDefaults,
13+
GlobalData,
14+
GlobalStores,
1315
} from '@dd/core/types';
1416
import { getContext } from '@dd/factory/helpers/context';
1517
import chalk from 'chalk';
@@ -183,6 +185,27 @@ export const getWorkspaces = async (
183185

184186
// TODO: Update this, it's a bit hacky.
185187
export const getSupportedBundlers = (getPlugins: GetPlugins) => {
188+
const data: GlobalData = {
189+
bundler: {
190+
name: 'esbuild',
191+
fullName: 'esbuild',
192+
variant: '',
193+
version: '1.0.0',
194+
},
195+
metadata: {},
196+
env: 'test',
197+
packageName: '@datadog/esbuild-plugin',
198+
version: '0',
199+
};
200+
201+
const stores: GlobalStores = {
202+
errors: [],
203+
warnings: [],
204+
logs: [],
205+
timings: [],
206+
queue: [],
207+
};
208+
186209
const arg: GetPluginsArg = {
187210
options: {
188211
telemetry: {},
@@ -198,26 +221,11 @@ export const getSupportedBundlers = (getPlugins: GetPlugins) => {
198221
// We don't care, this is a hack.
199222
start: 0,
200223
options: {} as OptionsWithDefaults,
201-
data: {
202-
bundler: {
203-
name: 'esbuild',
204-
fullName: 'esbuild',
205-
variant: '',
206-
version: '1.0.0',
207-
},
208-
metadata: {},
209-
env: 'test',
210-
packageName: '@datadog/esbuild-plugin',
211-
version: '0',
212-
},
213-
stores: {
214-
errors: [],
215-
warnings: [],
216-
logs: [],
217-
timings: [],
218-
queue: [],
219-
},
224+
data,
225+
stores,
220226
}),
227+
data,
228+
stores,
221229
bundler: {},
222230
};
223231

0 commit comments

Comments
 (0)