@@ -6,12 +6,15 @@ import { task } from "hereby";
6
6
import _glob from "glob" ;
7
7
import util from "util" ;
8
8
import chalk from "chalk" ;
9
- import { exec , readJson , getDiffTool , getDirSize , memoize , needsUpdate } from "./scripts/build/utils.mjs" ;
10
- import { runConsoleTests , refBaseline , localBaseline , refRwcBaseline , localRwcBaseline } from "./scripts/build/tests.mjs" ;
9
+ import { exec , readJson , getDiffTool , getDirSize , memoize , needsUpdate , Debouncer , Deferred } from "./scripts/build/utils.mjs" ;
10
+ import { runConsoleTests , refBaseline , localBaseline , refRwcBaseline , localRwcBaseline , cleanTestDirs } from "./scripts/build/tests.mjs" ;
11
11
import { buildProject as realBuildProject , cleanProject , watchProject } from "./scripts/build/projects.mjs" ;
12
12
import { localizationDirectories } from "./scripts/build/localization.mjs" ;
13
13
import cmdLineOptions from "./scripts/build/options.mjs" ;
14
14
import esbuild from "esbuild" ;
15
+ import chokidar from "chokidar" ;
16
+ import { EventEmitter } from "events" ;
17
+ import { CancelToken } from "@esfx/canceltoken" ;
15
18
16
19
const glob = util . promisify ( _glob ) ;
17
20
@@ -191,6 +194,7 @@ async function runDtsBundler(entrypoint, output) {
191
194
* @property {string[] } [external]
192
195
* @property {boolean } [exportIsTsObject]
193
196
* @property {boolean } [treeShaking]
197
+ * @property {esbuild.WatchMode } [watchMode]
194
198
*/
195
199
function createBundler ( entrypoint , outfile , taskOptions = { } ) {
196
200
const getOptions = memoize ( async ( ) => {
@@ -269,7 +273,7 @@ function createBundler(entrypoint, outfile, taskOptions = {}) {
269
273
270
274
return {
271
275
build : async ( ) => esbuild . build ( await getOptions ( ) ) ,
272
- watch : async ( ) => esbuild . build ( { ...await getOptions ( ) , watch : true , logLevel : "info" } ) ,
276
+ watch : async ( ) => esbuild . build ( { ...await getOptions ( ) , watch : taskOptions . watchMode ?? true , logLevel : "info" } ) ,
273
277
} ;
274
278
}
275
279
@@ -461,7 +465,7 @@ export const dts = task({
461
465
462
466
463
467
const testRunner = "./built/local/run.js" ;
464
-
468
+ const watchTestsEmitter = new EventEmitter ( ) ;
465
469
const { main : tests , watch : watchTests } = entrypointBuildTask ( {
466
470
name : "tests" ,
467
471
description : "Builds the test infrastructure" ,
@@ -482,6 +486,11 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
482
486
"mocha" ,
483
487
"ms" ,
484
488
] ,
489
+ watchMode : {
490
+ onRebuild ( ) {
491
+ watchTestsEmitter . emit ( "rebuild" ) ;
492
+ }
493
+ }
485
494
} ,
486
495
} ) ;
487
496
export { tests , watchTests } ;
@@ -625,6 +634,117 @@ export const runTests = task({
625
634
// " --shardId": "1-based ID of this shard (default: 1)",
626
635
// };
627
636
637
+ export const runTestsAndWatch = task ( {
638
+ name : "runtests-watch" ,
639
+ dependencies : [ watchTests ] ,
640
+ run : async ( ) => {
641
+ if ( ! cmdLineOptions . tests && ! cmdLineOptions . failed ) {
642
+ console . log ( chalk . redBright ( `You must specifiy either --tests/-t or --failed to use 'runtests-watch'.` ) ) ;
643
+ return ;
644
+ }
645
+
646
+ let watching = true ;
647
+ let running = true ;
648
+ let lastTestChangeTimeMs = Date . now ( ) ;
649
+ let testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
650
+ let testsChangedCancelSource = CancelToken . source ( ) ;
651
+
652
+ const testsChangedDebouncer = new Debouncer ( 1_000 , endRunTests ) ;
653
+ const testCaseWatcher = chokidar . watch ( [
654
+ "tests/cases/**/*.*" ,
655
+ "tests/lib/**/*.*" ,
656
+ "tests/projects/**/*.*" ,
657
+ ] , {
658
+ ignorePermissionErrors : true ,
659
+ alwaysStat : true
660
+ } ) ;
661
+
662
+ process . on ( "SIGINT" , endWatchMode ) ;
663
+ process . on ( "SIGKILL" , endWatchMode ) ;
664
+ process . on ( "beforeExit" , endWatchMode ) ;
665
+ watchTestsEmitter . on ( "rebuild" , onRebuild ) ;
666
+ testCaseWatcher . on ( "all" , onChange ) ;
667
+
668
+ while ( watching ) {
669
+ const promise = testsChangedDeferred . promise ;
670
+ const token = testsChangedCancelSource . token ;
671
+ if ( ! token . signaled ) {
672
+ running = true ;
673
+ try {
674
+ await runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false , { token, watching : true } ) ;
675
+ }
676
+ catch {
677
+ // ignore
678
+ }
679
+ running = false ;
680
+ }
681
+ if ( watching ) {
682
+ console . log ( chalk . yellowBright ( `[watch] test run complete, waiting for changes...` ) ) ;
683
+ await promise ;
684
+ }
685
+ }
686
+
687
+ function onRebuild ( ) {
688
+ beginRunTests ( testRunner ) ;
689
+ }
690
+
691
+ /**
692
+ * @param {'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir' } eventName
693
+ * @param {string } path
694
+ * @param {fs.Stats | undefined } stats
695
+ */
696
+ function onChange ( eventName , path , stats ) {
697
+ switch ( eventName ) {
698
+ case "change" :
699
+ case "unlink" :
700
+ case "unlinkDir" :
701
+ break ;
702
+ case "add" :
703
+ case "addDir" :
704
+ // skip files that are detected as 'add' but haven't actually changed since the last time tests were
705
+ // run.
706
+ if ( stats && stats . mtimeMs <= lastTestChangeTimeMs ) {
707
+ return ;
708
+ }
709
+ break ;
710
+ }
711
+ beginRunTests ( path ) ;
712
+ }
713
+
714
+ /**
715
+ * @param {string } path
716
+ */
717
+ function beginRunTests ( path ) {
718
+ if ( testsChangedDebouncer . empty ) {
719
+ console . log ( chalk . yellowBright ( `[watch] tests changed due to '${ path } ', restarting...` ) ) ;
720
+ if ( running ) {
721
+ console . log ( chalk . yellowBright ( "[watch] aborting in-progress test run..." ) ) ;
722
+ }
723
+ testsChangedCancelSource . cancel ( ) ;
724
+ testsChangedCancelSource = CancelToken . source ( ) ;
725
+ }
726
+
727
+ testsChangedDebouncer . enqueue ( ) ;
728
+ }
729
+
730
+ function endRunTests ( ) {
731
+ lastTestChangeTimeMs = Date . now ( ) ;
732
+ testsChangedDeferred . resolve ( ) ;
733
+ testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
734
+ }
735
+
736
+ function endWatchMode ( ) {
737
+ if ( watching ) {
738
+ watching = false ;
739
+ console . log ( chalk . yellowBright ( "[watch] exiting watch mode..." ) ) ;
740
+ testsChangedCancelSource . cancel ( ) ;
741
+ testCaseWatcher . close ( ) ;
742
+ watchTestsEmitter . off ( "rebuild" , onRebuild ) ;
743
+ }
744
+ }
745
+ } ,
746
+ } ) ;
747
+
628
748
export const runTestsParallel = task ( {
629
749
name : "runtests-parallel" ,
630
750
description : "Runs all the tests in parallel using the built run.js file." ,
0 commit comments