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' ;
620import { 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' ;
834import 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
1239const 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
1948const logMock = jest . mocked ( console . log ) ;
2049const errorMock = jest . mocked ( console . error ) ;
@@ -23,6 +52,31 @@ const warnMock = jest.mocked(console.warn);
2352// Access logs and strip colors.
2453const 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+
2680describe ( '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} ) ;
0 commit comments