Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

customize file watch path spec to get rid of warnings from chokidar #2022

Closed
adjenks opened this issue Sep 17, 2020 · 30 comments · Fixed by #2077
Closed

customize file watch path spec to get rid of warnings from chokidar #2022

adjenks opened this issue Sep 17, 2020 · 30 comments · Fixed by #2077

Comments

@adjenks
Copy link

adjenks commented Sep 17, 2020

Is this a feature request or a bug?

Bug/feature? Both? A feature to avoid the bug

What is the current behavior?

running web-ext run triggers too many watchers and it throws errors:

Error from chokidar : Error: ENOSPC: System limit for number of file watchers reached

What is the expected or desired behavior?

I would like to customize which files are watched so it doesn't output a long list of warnings for every file in the node_modules folder.

Version information (for bug reports)

  • Firefox version:
  • Your OS and version:
  • Paste the output of these commands:
node --version && npm --version && web-ext --version

v12.18.4
6.14.6
5.0.0

@Rob--W
Copy link
Member

Rob--W commented Sep 17, 2020

Does the --watch-file option offer the desired functionality? It was introduced in #1784 and allows you to specify one file to watch.

It could easily be changed to support multiple files and/or directories, by
allowing --watch-file to be specified multiple times at

web-ext/src/program.js

Lines 596 to 602 in 11faea6

'watch-file': {
describe: 'Reload the extension only when the contents of this' +
'file changes. This is useful if you use a custom' +
' build process for your extension',
demandOption: false,
type: 'string',
},

and using the multiple values at

web-ext/src/watcher.js

Lines 54 to 70 in 11faea6

log.debug(`Watching for file changes in ${watchFile || sourceDir}`);
const watchedDirs = [];
const watchedFiles = [];
if (watchFile) {
if (fs.existsSync(watchFile) && !fs.lstatSync(watchFile).isFile()) {
throw new UsageError('Invalid --watch-file value: ' +
`"${watchFile}" is not a file.`);
}
watchedFiles.push(watchFile);
} else {
watchedDirs.push(sourceDir);
}
watcher.watch(watchedFiles, watchedDirs, Date.now());

@adjenks
Copy link
Author

adjenks commented Sep 17, 2020

Yes, that would work if I could specify a directory with exclusions. I want to specify the main directory, but not the node_modules folder. Where is this feature documented? I couldn't find it anywhere. Perhaps it's too new.

@Rob--W
Copy link
Member

Rob--W commented Sep 18, 2020

It's currently only listed when you run web-ext run --help.

It should also be documented at the Extension workshop, whose source code is at https://github.com/mozilla/extension-workshop/blob/master/src/content/documentation/develop/web-ext-command-reference.md (permalink to current version).

I suppose that we can fix this bug first and then document the behavior over there.

@tgiardina
Copy link

it looks like this code is relevant (from src/watcher.js lines 56-68):

const watchedDirs = [];
const watchedFiles = [];

if (watchFile) {
  if (fs.existsSync(watchFile) && !fs.lstatSync(watchFile).isFile()) {
    throw new UsageError('Invalid --watch-file value: ' +
      `"${watchFile}" is not a file.`);
  }

  watchedFiles.push(watchFile);
} else {
  watchedDirs.push(sourceDir);
}

However, I was unable to easily figure out how you guys handle globs, so I'm going to need guidance before venturing a PR.

Also, we'd have to change --watch-file to --watch-files to be consistent with --ignore-files. I'm not sure if this constitutes a break change seeing how --watch-file isn't documented.

@Rob--W
Copy link
Member

Rob--W commented Oct 3, 2020

Good question!

To watch files, we are using watchpack @ https://github.com/webpack/watchpack

The existing glob implementation is implemented at https://github.com/mozilla/web-ext/blob/5.1.0/src/util/file-filter.js and relies on themultimatch library @ https://github.com/sindresorhus/multimatch

watchpack currently takes a list of files/directories upfront, whereas file-filter / multimatch uses functions to lazily filter file/directory names.

There are about three ways to implement globs:

  • Recursively read the contents of the directories to try and find all files and directories upfront. The disadvantage of this is that it's expensive, and that it could result in missing out on files that were created after the watcher started.
  • Look at watchpack's source code and investigate whether there is a way to filter by a given function, and if not, whether it makes sense to add that functionality.
  • (third option: not implementing globbing at all)

I prefer option 2. For that the best way forwards is to ask watchpack whether they are open to adding an option to take a function to decide whether or not to watch a file, e.g. by passing a function to the ignored option (which currently takes a string or RegExp).

@tgiardina
Copy link

@Rob--W, Thanks for the guidance! I hope to have time for a PR sometime over the next few weeks.

@ankushduacodes
Copy link
Contributor

@Rob--W I would like to take on this issue. Please let me know if I may.

@tgiardina
Copy link

@Rob--W and @ankushduacodes,

Sorry I haven't ventured a PR yet! I got caught up in other projects. If @ankushduacodes wants to take a shot at it, that's fine by me.

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 2, 2020

@tgiardina If you have already started then it's fine, You can work on the issue. if not then I can take over. Please let me know

@tgiardina
Copy link

@ankushduacodes I have not, so it's all you.

@ankushduacodes
Copy link
Contributor

@Rob--W, I am new to open-source, I would really appreciate if you can give me some pointers related to this issue and how to solve it.

@rpl
Copy link
Member

rpl commented Nov 3, 2020

@Rob--W, I am new to open-source, I would really appreciate if you can give me some pointers related to this issue and how to solve it.

Hi @ankushduacodes, @Rob--W is currently away and so I'll take over the mentoring on this "contrib: welcome" bug.

Follows some details that should help you to get started on this issue, let me know if it does help or if there is something that is still unclear and you would like to clear out before starting to work on this.

Setup dev environment

If this is the first web-ext bug you are looking into, I suggest to start by read our CONTRIBUTING.md file here, then clone the repo and try to run the existing tests (as described in the contributing guide) to make sure your development environment is correctly configured.

Understand the issue (and possibly be able to reproduce)

A common initial step when starting to work on an issue is to be sure to understand the issue (and possibly be able to reproduce the issue locally).
The error that the issue description does mention seems a Linux error code, if you are on Linux you may be able to reproduce the issue as described in the first comment, otherwise the error or the limit may differ (I'll provide some other details about how to try to trigger the issue locally at the end of this comment).

Plan a fix

From a quick read to the comments above (in particular comments #2022 (comment), #2022 (comment) and #2022 (comment)) and after taking a look to the watchpack's README here I think that the simpler and nicer way to fix this issue may be to:

  • make use of the Watchpack's ignored option described in the README section linked above:
var wp = new Watchpack({
...
	ignored: "**/.git"
	// ignored: "string" - a glob pattern for files or folders that should not be watched
	// ignored: ["string", "string"] - multiple glob patterns that should be ignored
	// ignored: /regexp/ - a regular expression for files or folders that should not be watched
	// All subdirectories are ignored too
});
  • define a new web-ext run cli option, e.g. one named --watch-ignored, which can be specified multiple times with one or more glob patterns (which should then be used as the ignored option when we do create the Watchpack instance)

The relevant part of the web-ext source code is linked in Rob's comment #2022 (comment)

Cover the change with new tests

Every change should be covered by tests, to make sure that it works as expected in the scenario we care about, and also to make sure that the fix doesn't regress when we are making other (potentially totally unrelated) changes in the future.

For this particular change, I think that we may want to add a unit test in tests/unit/test.watcher.js.

Taking a look to the other unit test defined in the same test file (as well as other unit tests in the repository) is a good way to get a picture of how to write the new unit test (but also feel free to ping us if you need help to figure out some issue you may get stuck on).


Reproducing the issue

For this particular bug, it looks that to be able to reproduce the issue we could:

  • create a small test extension (even just getting one from the mdn/webextensions-examples repo here)
  • then create a subdirectory (inside the directory where the extension sources are) with a considerable amount of files into it (which I guess it can also be achieved by using npm install to install a number of dependency nodejs packages, the extension doesn't need to be really using those dependencies, just having a huge number of files in the directory that web-ext run will be watching should be enough to trigger the issue)
  • finally run web-ext run -s path/to/test-extension-dir and see if the issue is triggered as expected (if it is not, increase the number of files in the directory even more until you finally hit the limit).

The error that is mentioned in the issue description is likely system dependent (the ENOSPC is definitely the one that linux would produce in those condition, the error code and actualy limit on macOS and windows may likely differ)

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 12, 2020

@rpl I will be working on this one. As you said in this comment that this issue is related to linux environment, so I have set up an Ubuntu virtual machine. But I also have mac and windows envs side loaded and will be testing those out for this issue as well.

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 17, 2020

@rpl I was quiet busy for last few days as I had my mid term exams and didnt get much time to look into it further, I will actively working on this issue from today onwards, Thank you for your patience 😊

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 18, 2020

@rpl, I was trying to run web-ext run by specifying a file path to watch and I am not sure if the understand the usage of the lines of code below:
https://github.com/mozilla/web-ext/blob/master/src/watcher.js#L60-L63

    if (fs.existsSync(watchFile) && !fs.lstatSync(watchFile).isFile()) {
      throw new UsageError('Invalid --watch-file value: ' +
        `"${watchFile}" is not a file.`);
    }

what is supposed to accomplish, One would think of throwing an error if the file path specified by the user using command line argument --watch-file is not valid or is not a file, but seeing the if check in above-specified lines of code does the opposite?.... Could you please elaborate on what is going on here?

Whenever I use --watch-file option, it never raises UsageError, no matter if I provide a valid path or not. I am really not sure what is going on there.

@rpl
Copy link
Member

rpl commented Nov 18, 2020

@rpl, I was trying to run web-ext run by specifying a file path to watch and I am not sure if the understand the usage of the lines of code below:
https://github.com/mozilla/web-ext/blob/master/src/watcher.js#L60-L63

    if (fs.existsSync(watchFile) && !fs.lstatSync(watchFile).isFile()) {
      throw new UsageError('Invalid --watch-file value: ' +
        `"${watchFile}" is not a file.`);
    }

what is supposed to accomplish, One would think of throwing an error if the file path specified by the user using command line argument --watch-file is not valid or is not a file, but seeing the if check in above-specified lines of code does the opposite?.... Could you please elaborate on what is going on here?
Whenever I use --watch-file option, it never raises UsageError, no matter if I provide a valid path or not. I am really not sure what is going on there.

@ankushduacodes I expect that error to be triggered if you pass a path to a non file (e.g. a directory or a unix socket file etc.) as the --watch-file value.

As an example, let's say that ./src is a directory then I expect that web-ext run --watch-file ./src to report that error to the user.

I just tried it locally and it did fail with that error for me, as I was expecting.

Let me know if these additional details did help and/or if you still need help to be able to reproduce the issue locally.

@ankushduacodes
Copy link
Contributor

@rpl, I was able to reproduce the issue with no complications.
I was trying to understand what --watch-file does and It's just that I am not able to make it throw UsageError, I just tried to run it again using web-ext run -s ~/Documents/GitHub/web-ext/borderify/ --watch-file ./src --verbose
It not only opened up the firefox but also it was able work like nothing is wrong with --watch-file,
here are the debug logs:

[program.js][info] Version: master-4688bb74896ff6f8570d7396b22a2144432bd8ec
[program.js][debug] Discovering config files. Set --no-config-discovery to disable
[config.js][debug] Discovered config "/Users/ankushdua/.web-ext-config.js" does not exist or is not readable
[config.js][debug] Discovered config "/Users/ankushdua/web-ext-config.js" does not exist or is not readable
[config.js][debug] Discovered config "/Users/ankushdua/package.json" does not exist or is not readable
[cmd/run.js][info] Running web extension from /Users/ankushdua/Documents/GitHub/web-ext/borderify
[util/manifest.js][debug] Validating manifest at /Users/ankushdua/Documents/GitHub/web-ext/borderify/manifest.json
[extension-runners/firefox-desktop.js][debug] Creating new Firefox profile
[firefox/index.js][debug] Running Firefox with profile at /var/folders/fx/4ldx407s32gfyy32ngql6_bc0000gn/T/aa7c76fc-58f9-451d-b8af-8aa11f799018
[firefox/index.js][debug] Executing Firefox binary: /Applications/Firefox.app/Contents/MacOS/firefox-bin
[firefox/index.js][debug] Firefox args: -start-debugger-server 50771 -foreground -no-remote -profile /var/folders/fx/4ldx407s32gfyy32ngql6_bc0000gn/T/aa7c76fc-58f9-451d-b8af-8aa11f799018
[firefox/index.js][info] Use --verbose or open Tools > Web Developer > Browser Console to see logging
[firefox/remote.js][debug] Connecting to the remote Firefox debugger
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (0); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (1); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (2); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (3); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (4); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (5); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (6); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (7); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Retrying Firefox (8); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/index.js][debug] Firefox stdout: Started devtools server on 50771
[firefox/remote.js][debug] Retrying Firefox (9); connection error: Error: connect ECONNREFUSED 127.0.0.1:50771
[firefox/remote.js][debug] Connecting to Firefox on port 50771
[firefox/remote.js][debug] Connected to the remote Firefox debugger on port 50771
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.917 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSButton: 0x12ef9b800>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.918 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSButton: 0x13a5e3c00>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.919 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSButton: 0x13a5e6c00>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.920 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSButton: 0x13a5e6400>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.921 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSButton: 0x13a5e4000>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.922 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSPopoverTouchBarItemButton: 0x13a5e4800>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/index.js][debug] Firefox stderr: 2020-11-19 05:35:58.923 firefox-bin[1490:30983] Warning: Expected min height of view: (<NSPopoverTouchBarItemButton: 0x13aa1d400>) to be less than or equal to 30 but got a height of 32.000000. This error will be logged once per view in violation.
[firefox/remote.js][debug] installTemporaryAddon: {"addon":{"id":"4513605365a1444cae866b986f47337e70804443@temporary-addon","actor":false},"from":"server1.conn0.addonsActor2"}
[firefox/remote.js][info] Installed /Users/ankushdua/Documents/GitHub/web-ext/borderify as a temporary add-on
[cmd/run.js][info] The extension will reload if any source file changes
[util/file-filter.js][debug] Resolved path **/*.xpi with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/*.xpi
[util/file-filter.js][debug] Resolved path **/*.zip with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/*.zip
[util/file-filter.js][debug] Resolved path **/.* with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/.*
[util/file-filter.js][debug] Resolved path **/.*/**/* with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/.*/**/*
[util/file-filter.js][debug] Resolved path **/node_modules with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/node_modules
[util/file-filter.js][debug] Resolved path **/node_modules/**/* with sourceDir /Users/ankushdua/Documents/GitHub/web-ext/borderify to /Users/ankushdua/Documents/GitHub/web-ext/borderify/**/node_modules/**/*
[watcher.js][debug] Watching for file changes in ./src
[watcher.js][debug] [ './src' ]
[watcher.js][debug] []
[extension-runners/index.js][info] Press R to reload (and Ctrl-C to quit)
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
[firefox/index.js][debug] Firefox stderr: JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.

I could be doing something wrong here..

(PS: I know the explanation of if by me in this comment, I think I misunderstood the functionality of fs.existsSync(), I thought it returns true if a given path is a file and is a valid path, now that I look at it again and read some documentation about it, I understand that it only checks for a valid path).

@ankushduacodes
Copy link
Contributor

@rpl, Do you want me to create a new instance watchpack for ignored files? or just integrate it with the existing watcher?

@rpl
Copy link
Member

rpl commented Nov 19, 2020

@rpl, Do you want me to create a new instance watchpack for ignored files? or just integrate it with the existing watcher?

It should be configured on the Watchpack instance created in the onSourceChange function exported by src/watcher.js.

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 19, 2020

@rpl Could you please look into this comment so I know what I am doing wrong.

@rpl
Copy link
Member

rpl commented Nov 19, 2020

@rpl Could you please look into this [comment}(https://github.com//issues/2022#issuecomment-730043235) so I know what I am doing wrong.

My guess is that there was no directory (or other "non-file" directory entry) named src in the current working dir where you have executed the command, if there is no src file in the current directory the issue will not be raised as you should expect.

e.g. Try this

web-ext run -s ~/Documents/GitHub/web-ext/borderify/ --watch-file web-ext run -s ~/Documents/GitHub/web-ext/borderify

Given that borderify is definitely a directory and it does exist for sure, this should confirm you that the issue is what I was guessing it may be.

In general when you have doubts about what is going on, it is a good idea to add some additional temporary logging in your local clone and trigger the behavior again to be able to inspect the value of some of the pieces that you have doubts about (e.g. you may have added a temporary call to console.log right before that if and do something like console.log("TMP DEBUG usage error watchfile check:", watchFile, fs.existsSync(watchFile), fs.lstatSync(watchFile).isFile()), then build web-ext and run the command again with your additional logging and review those logs to better understand how those checks are behaving and what those calls returns).

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 19, 2020

Understood Thank you!...

So Is it an intended design that if watch-file gets a directory which does not exist, then it just ignores that provided directory is invalid?
Shouldn't it let the user know at least that given directory was not valid so watcher is watching whole source directory for change?

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Nov 20, 2020

@rpl, I have made a pull request, although I have not written tests yet, Please check it out so I know if I am on the right path or do I need to change something.

@rpl
Copy link
Member

rpl commented Nov 30, 2020

@rpl, I have made a pull request, although I have not written tests yet, Please check it out so I know if I am on the right path or do I need to change something.

@ankushduacodes 👍 based on a very quick look to #2077 it seems that you are on the right track (I haven't pull the changes and tried them locally, but I looked to the diff and the changes seems to be propagating the new option to the onSourceChange function exported by the watcher module as I was expecting).

Let's proceed to the next step: adding some new unit tests
A reasonable starting point would be to define a new test case in tests/unit/test.watcher.js that covers the changes to the watcher.js module (after that it seems reasonable to add some other tests that make sure that the option is propagated to the watcher helper function from the src/cmd/run.js module as we expect).

@ankushduacodes
Copy link
Contributor

ankushduacodes commented Dec 3, 2020

@rpl I was thinking of writing a test that looks something like the following code:

describe('--watch-ignored is passed in', () => {
    it('does not change if ignored file is touched', async () => {
      const {
        onChange,
      } = await watchChange({
        touchedFile: 'foo.txt',
        watchIgnored: Array('foo.txt', 'bar.txt'),
      });
      sinon.assert.notCalled(onChange);
    });
  });

and watchChange function looks something like this:

const watchChange = ({
    watchFile,
    touchedFile,
    watchIgnored,
  }: AssertWatchedParams = {}) => withTempDir(
    (tmpDir) => {
      const artifactsDir = path.join(tmpDir.path(), 'web-ext-artifacts');
      const someFile = path.join(tmpDir.path(), touchedFile);

      if (watchFile) {
        watchFile = path.join(tmpDir.path(), watchFile);
      }

      var resolveChange;
      const whenFilesChanged = new Promise((resolve) => {
        resolveChange = resolve;
      });
      const onChange = sinon.spy(() => {
        resolveChange();
      });

      let watchedFilePath;
      let watchedDirPath;
      let ignoredFilePaths;

      if (watchIgnored) {
        watchIgnored.forEach((file, index, arr) => {
          arr[index] = path.join(tmpDir.path(), file);
        });
      }

      return fs.writeFile(someFile, '<contents>')
        .then(() => {
          return onSourceChange({
            sourceDir: tmpDir.path(),
            watchFile,
            watchIgnored,
            artifactsDir,
            onChange,
            shouldWatchFile: () => true,
          });
        })
        .then((watcher) => {
          const watchedFile = watcher.fileWatchers[0];
          const watchedDir = watcher.dirWatchers[0];
          ignoredFilePaths = watchIgnored;
          watchedFilePath = watchedFile && watchedFile.path;
          watchedDirPath = watchedDir && watchedDir.path;

          return watcher;
        })
        .then((watcher) => {
          return fs.utimes(someFile, Date.now() / 1000, Date.now() / 1000)
            .then(() => watcher);
        }).then((watcher) => {
          const assertParams = {
            onChange,
            watchedFilePath,
            watchedDirPath,
            tmpDirPath: tmpDir.path(),
            ignoredFilePaths,
          };

          return Promise.race([
            whenFilesChanged
              .then(() => {
                watcher.close();
                // This delay seems to avoid stat errors from the watcher
                // which can happen when the temp dir is deleted (presumably
                // before watcher.close() has removed all listeners).
                return new Promise((resolve) => {
                  setTimeout(resolve, 2, assertParams);
                });
              }),
            // Time out if no files are changed
            new Promise((resolve) => setTimeout(() => {
              watcher.close();
              resolve(assertParams);
            }, 500)),
          ]);
        });
    }

But the problem is the assertion is failing because the onChange function is being called once but this shouldn't be happening.
Now to my understanding, onChange function is only being called if it detects a change in the file or folder it is watching, but if the file that is being changed is to be ignored it should not be called as seen in behavior of point 2 below

Here's what I have tried so far:
1: Ran web-ext run -s ~/Documents/GitHub/web-ext/borderify/ -t firefox-desktop --watch-ignored ~/Documents/GitHub/web-ext/borderify/manifest.json --verbose

  • Behaviour: as expected, if I make any change to manifest.json, there is no reload. but if I make change to any other file in the borderify folder such as borderify.js or README.md, the extension successfully reloads

2: Ran web-ext run -s ~/Documents/GitHub/web-ext/borderify/ -t firefox-desktop --watch-ignored ~/Documents/GitHub/web-ext/borderify/manifest.json --watch-file ~/Documents/GitHub/web-ext/borderify/manifest.json --verbose

  • Behaviour: To my surprise, --watch-ignored was given priority over --watch-file, and when I made and saved changes to manifest.json, the extension was not reloaded.

3: Ran web-ext run -s ~/Documents/GitHub/web-ext/borderify/ -t firefox-desktop --watch-file ~/Documents/GitHub/web-ext/borderify/manifest.json --verbose

  • Behaviour: as expected, if I make any change to manifest.json, Only then the extension is reloaded.

4: Ran web-ext run -s ~/Documents/GitHub/web-ext/borderify/ -t firefox-desktop --watch-ignored ~/Documents/GitHub/web-ext/borderify/manifest.json ~/Documents/GitHub/web-ext/borderify/README.md --verbose

  • Behaviour: as expected, if I make any change to manifest.json or README.md, there is no reload. But if I make a change to any other file in the borderify folder such as borderify.js, the extension successfully reloads

Here is the watcher that is being returned with I pass in --watch-ignored:

EventEmitter {
  _events: [Object: null prototype] { change: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  options: {
    ignored: [
      '/Users/ankushdua/Documents/GitHub/web-ext/borderify/manifest.json',
      '/Users/ankushdua/Documents/GitHub/web-ext/borderify/README.md'
    ],
    aggregateTimeout: 200
  },
  watcherOptions: {
    ignored: [
      '/Users/ankushdua/Documents/GitHub/web-ext/borderify/manifest.json',
      '/Users/ankushdua/Documents/GitHub/web-ext/borderify/README.md'
    ],
    poll: undefined
  },
  fileWatchers: [],
  dirWatchers: [
    Watcher {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      directoryWatcher: [DirectoryWatcher],
      path: '/Users/ankushdua/Documents/GitHub/web-ext/borderify',
      startTime: 1606979783950,
      data: 0,
      [Symbol(kCapture)]: false
    }
  ],
  mtimes: [Object: null prototype] {},
  paused: false,
  aggregatedChanges: [],
  aggregatedRemovals: [],
  aggregateTimeout: 0,
  _onTimeout: [Function: bound _onTimeout],
  [Symbol(kCapture)]: false
}

Here is the watcher that is being returned with I DON'T pass in --watch-ignored:

EventEmitter {
  _events: [Object: null prototype] { change: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  options: { aggregateTimeout: 200 },
  watcherOptions: { ignored: undefined, poll: undefined },
  fileWatchers: [
    Watcher {
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      directoryWatcher: [DirectoryWatcher],
      path: '/Users/ankushdua/Documents/GitHub/web-ext/borderify/manifest.json',
      startTime: 1606979053531,
      data: 0,
      [Symbol(kCapture)]: false
    }
  ],
  dirWatchers: [],
  mtimes: [Object: null prototype] {},
  paused: false,
  aggregatedChanges: [],
  aggregatedRemovals: [],
  aggregateTimeout: 0,
  _onTimeout: [Function: bound _onTimeout],
  [Symbol(kCapture)]: false
}

(PS: All of the above mentioned testing was done in both macos and linux)

@rpl
Copy link
Member

rpl commented Dec 3, 2020

@ankushduacodes the issue you are facing while trying to write the unit test is likely the fact that the watchpack version we are using is still emitting onChange events for the ignored files as part of an "initial scan" (see https://github.com/webpack/watchpack/blob/f7ef8e0a5266f78242abe21faa34c5d56eecd21f/lib/DirectoryWatcher.js#L289).

To keep the test simple I think we can avoid to use (and change) that watchChange test helper (which is also pretty unreadable, in a separate pull request we should really rewrite it with async function and await to make it easier to read), and just cover the test by using directly onSourceChange in this particular test case itself.

To make it even simpler to write the test and get it to better cover the scenario we are testing, I think we could also add a debounceTime parameter to onSourceChange so that we can reduce the debounce time while running the test (and avoid to wait the current default 1 second debounce time).

The resulting test case would look more or less like this:

 describe('--watch-ignored is passed in', () => {
   it('does not change if ignored file is touched', () =>
     withTempDir(async (tmpDir) => {
       const debounceTime = 10;
       const onChange = sinon.spy();
       const tmpPath = tmpDir.path();
       const files = ['foo.txt', 'bar.txt', 'foobar.txt'].map(
         (filePath) => path.join(tmpPath, filePath)
       );

       const watcher = onSourceChange({
         sourceDir: tmpPath,
         artifactsDir: path.join(tmpPath, 'web-ext-artifacts'),
         onChange,
         watchIgnored: ['foo.txt'],
         shouldWatchFile: () => true,
         debounceTime,
       });

       async function waitDebounce() {
         await new Promise((resolve) => setTimeout(resolve, debounceTime * 2));
       }

       // Verify foo.txt is being ignored.
       await fs.writeFile(files[0], '<content>');
       sinon.assert.notCalled(onChange);

       // Verify that the other two files are not be ignored.
       await fs.writeFile(files[1], '<content>');
       await waitDebounce();
       sinon.assert.calledOnce(onChange);

       await fs.writeFile(files[2], '<content>');
       await waitDebounce();
       sinon.assert.calledTwice(onChange);

       watcher.close();
        // Leave watcher.close some time to complete its cleanup before withTempDir will remove the
        // test directory. 
        await waitDebounce();
     }));
 });

Let me know if this alternative test strategies does work as it should (you'll need to also make the needed changes to src/watcher.js to add the debounceTime parameter, which should default to the current debounce time hardcoded in the method).

As an additional side note:
don't be afraid to add code, especially tests, that doesn't work yet in the pull requests you are working on.
If you push your changes in the PR, then we will be able to:

  • look to the failures on the CI without even having to pull the PR branch locally (and in some cases that may be enough for us to provide you some ideas about things to look into)
  • or we can just pull the PR branch as is and be able to reproduce the issue you are facing
    which would allow us to provide a feedback quicker.

@ankushduacodes
Copy link
Contributor

@rpl This is just a friendly reminder for #2077, I have added all the necessary test and the doubts and updates are have been added in this comment, Hoping to hear back from you :)

@rpl
Copy link
Member

rpl commented Dec 10, 2020

@rpl This is just a friendly reminder for #2077, I have added all the necessary test and the doubts and updates are have been added in this comment, Hoping to hear back from you :)

Ouch, thanks for the ping, apparently I missed to notice the github notification when you updated the PR last week, sorry about that.
I'll take a look to the PR asap (likely between today and tomorrow)

@adjenks
Copy link
Author

adjenks commented Dec 22, 2020

Good work guys, thanks a million <3

@ankushduacodes
Copy link
Contributor

Opened a new issue 871 with extension-workshop repo to add docs for this feature.
Will be adding it soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants