karma-runner / karma

Spectacular Test Runner for JavaScript
http://karma-runner.github.io
MIT License
11.96k stars 1.71k forks source link

karma watch tends to fail with large streamed bundles #2420

Open ghost opened 8 years ago

ghost commented 8 years ago

i currently use gulp to produce two large JS bundles from TypeScript with browserify and factor-bundle. One bundle has all the main code and the other has all the specs.

The problem is when I use watch mode and update the bundles, karma detects two file change events on both files. I notice karma picks up the first change where the file is empty. This is confirmed by doing a readFileSync around here. Additionally, the syntax error message from the test execution also confirms this (i.e. Unexpected end of input).

Setting autoWatchBatchDelay to a higher number doesn't mitigate this problem. There is usually a 0.5 second delay between the first change event (empty file) and the second one (file generated). I'm not exactly sure what's going on there (maybe there's a race condition somewhere?) I remember adding a console.log(self.files) somewhere in the aforementioned section of code and since the output is very long, it takes time to flush to the terminal and the problem stops occurring (however waiting a long time is not a very productive activity!)

By enabling awaitWriteFinish in chokidar, the problem is mitigated. Maybe this is the solution?

https://github.com/karma-runner/karma/compare/master...khoomeister:ck-awaitwritefinish#diff-3c250c1426c199f7eccf17c774f0adfaR84

It'd be great to find a permanent fix for this as this is vital to our TDD workflow.

dignifiedquire commented 7 years ago

I don't think we should enable this by default, as it has some considerable drawbacks, as mentioned in https://github.com/paulmillr/chokidar. But it would be good to expose as an option to be configurable.

danielcompton commented 7 years ago

I did a bit of digging into this as I hit the same problem with ClojureScript compilation. As best as I can tell, what's happening is:

  1. Chokidar sets up a watcher for file change events
  2. A file is changed by compilation. The file changes are streamed in. Chokidar's docs mention that a 'change' event can be emitted before a file has finished being changed.
  3. Chokidar picks up the change and calls the fileList.changeFile callback
  4. In the changeFile callback, in part of the promise chain, return self._preprocess(file) is called.
  5. The preprocessor runs. If there is no preprocessor, an identity transform runs
  6. The processed file is saved somewhere for serving
  7. The rest of the file changes are saved
  8. No further events are called
  9. When the file is requested, the partially written version is served

In my case with CLJS compilation, a much better solution was to serve the written files, but not watch them, and only watch the root files which are written at the end of the compilation process:

        files: [
            root + '/goog/base.js',
            root + '/cljs_deps.js',
            root + '/test.js',// same as :output-to
            {pattern: root + '/**/*.+(cljs|cljc|clj|js|js.map)', included: false, watched: false}
        ],