AtomLinter / linter-eslint-node

ESLint plugin for Atom/Pulsar Linter (v8 and above)
https://web.pulsar-edit.dev/packages/linter-eslint-node
MIT License
4 stars 3 forks source link

Got even more tests working #5

Closed savetheclocktower closed 2 years ago

savetheclocktower commented 2 years ago

At this point, everything that's commented out is that way because it's testing stuff that just works differently in this package, or that I don't think is worth adding.

At this point, I'd be OK with putting out a first release if we can prove that it works for actual human beings on Windows, and not just in CI. The log statements should get cleaned up, of course, though I'll probably keep some in as console.debugs that log only when Atom is in dev mode.

Some things in here worth mentioning:

savetheclocktower commented 2 years ago

The other thing that occurred to me: we have no tests for the node-resolution logic or for the ability to specify per-project options via .linter-eslint. I'll start writing tests for the latter tonight, but the former feels hard.

If someone wants to contribute to this package in the future, I don't think we should make them download various specific versions of node into known locations on their drive just so we can run some specs. This feels like a CI task. Is it possible to define specs that only run on CI?

savetheclocktower commented 2 years ago

I knew those new config specs were too good to be true; they felt too easy to write. I have no idea why the “if we rename .linter-eslint” test is failing in the way that it is; that promise should reject after five seconds, not 60.

UziTech commented 2 years ago

The test runner is setup to give a ci a little bit more time. The default timeout for ci is 60 seconds.

UziTech commented 2 years ago

We can use environment variables to only run tests in certain environments.

savetheclocktower commented 2 years ago

OK, it turns out to be a problem with async tests in general. I can't get any sort of async strategy to work, including either of these simple test cases:

'use babel';

function wait (ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

describe('Test file', () => {
  it('does something with setTimeout', (done) => {
    setTimeout(() => {
      expect(typeof 1).toBe('number');
      done();
    }, 100);
  });

  it('does something with async/await', async () => {
    await wait(1000);
    expect(typeof 1).toBe('number');
  });
});

They both timeout. Experiencing this on both Windows and macOS, so there's something happening in the Jasmine internals that I don't understand, or perhaps in the transpilation. This despite the fact that all the existing tests that use await are working.

I wonder if it's got to do with the clock being mocked? If I do jasmine.clock().install() in a test, it errors with Error: Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?

savetheclocktower commented 2 years ago

OK, I was ready to give up on this several times, but I've got this test passing locally now on Windows, at least via GUI.

  1. The core issue on Windows is that when we set the project path, Atom starts an async job to set up a watcher on that path. We need to wait for that watcher to be ready before proceeding, or else it won't realize that we've renamed the file.
  2. Jasmine does indeed wrap setTimeout with something; preserving the unwrapped version and exporting it from the helpers file seems to do the trick. I was confused about 5-seconds-versus-60 because my untilConfigChanges function explicitly waits for 5000 ms before giving up, regardless of environment, but that wasn't doing anything because it was calling the wrapped version of setTimeout. Should work just fine now.

And as I write this I notice that CI is still failing on Windows, but at least it's passing on Linux. My woes continue.

savetheclocktower commented 2 years ago

That one failing test on Windows passes for me locally, so I don't know what's going on. It seems weird for such a simple test to be flaky.

UziTech commented 2 years ago

It doesn't seem to be just windows. It also seems like different tests are failing. I'm thinking we need to make the tests more granular so we aren't relying on atom as much.

savetheclocktower commented 2 years ago

The tests that claim to be failing are direct equivalents of tests in linter-eslint. I suspect the timeout errors are triggered by config-spec.js tests and just not getting matched up correctly, because I believe all those tests were passing before I started writing config tests.

But now that I've skipped the one test that was giving me trouble, it's still having sporadic timeout issues, so I'll try a couple more things. Having to diagnose test failures in CI that I can't reproduce on my machine might be my least favorite task in all of computer science.

savetheclocktower commented 2 years ago

OK, my nightmare is approaching an end. And yours, too, if you've been getting all these CI failure emails as well.

The new failures in linter-eslint-node-spec.js were caused by a Config.onConfigDidChange handler firing too early and making us think that nodeBin had changed when it hadn't. That caused the worker script to get killed and re-initialized with the “new” version of Node, which somehow was consistently happening inside of one particular test on Windows CI, and another particular test on Ubuntu CI. That caused a linting job to get orphaned and timeout.

Knowing now that those failures weren’t just weird side effects of config-spec.js specs, I took another look at those. Any failure that happened consistently in Windows CI is something that I could reproduce about one time out of ten locally. So, after a bunch of trial and error, I managed to isolate the flakiness to a race condition just after file modifications. await editor.save() triggers a file change modification, and those are not guaranteed to be synchronous. So whenever we save a file, or rename it, or something, we’ve got to cede control to be sure that those callbacks have run before we try to assert stuff.

In my experiments on my Windows machine, even await wait(0) was enough to get these tests passing consistently except maybe one time in a hundred. I made them all await wait(1000) just for safety. That got everything passing on all platforms.

I hate waiting for fixed amounts of time, and I’d be glad to find a different way to know when it’s safe for those tests to proceed, but the most obvious way to do that would be to use the same APIs that are part of what I’m testing in the first place. That feels icky, but if I have to revisit these tests, I will live with that icky feeling.

And, yes, it’s always an option to test Config in isolation. But the integration points between the main module and Config are important, too, because they’re the parts that save you from having to reload your project window whenever you make a change to your config. If these tests prove to be flaky no matter what I do, I'll rewrite them to call Config.rescan() and Config.update() manually.