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

CLI: Introduce QUnit CLI #1115

Merged
merged 2 commits into from
Mar 24, 2017
Merged

CLI: Introduce QUnit CLI #1115

merged 2 commits into from
Mar 24, 2017

Conversation

trentmwillis
Copy link
Member

@trentmwillis trentmwillis commented Mar 16, 2017

As discussed in #1024. This PR will introduce the bare-bones functionality of running tests.

Demo:

tap-reporter-qunit-cli

Todo:

  • Get new version of js-reporters for a better TapReporter (which is what tests are assuming)

bin/qunitjs Outdated
var walkSync = require( "walk-sync" );
var run = require( "./run" );

var pkg = fs.readJsonSync( path.resolve( __dirname, "..", "package.json" ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would require( '../package.json' ); work here? I believe require, unlike fs, is always scoped to where the code is authored, so it shouldn't need __dirname.

bin/run.js Outdated

module.exports = function run( files ) {
var QUnitFile = path.resolve( __dirname, "../dist/qunit.js" );
var QUnit = require( QUnitFile );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here :)

@Krinkle
Copy link
Member

Krinkle commented Mar 16, 2017

A few design questions.

Environment

Do we want to make individual test suites executable (even if just optional), or should we require a custom environment with certain predefined globals?
The current draft goes for the latter, which is similar to Mocha's command-line runner. This has the benefits of shorter test files (less bootstrapping), more control over the output, central place for configuration (command line options, or declarative settings file).
The potential downsides are: Requiring linters to know about the environment (ESLint already has a qunit env, though it whitelists way too much; we just need QUnit).
I don't think there are any other downsides, so.. looks good. It also has the benefit of allowing test suites to be shared between traditional browser environments and Node.js, since the former would effectively have QUnit predefined as well.

Source code

How will source code be called from the test suite? If we target Node.js only, then we can just prescribe users to call require() to import any relevant files or modules. On the other hand, this may complicate sharing of test suites with browser environments.

Or would it? That depends on the use case for running tests in Node.js:

  1. Testing in Node.js, in addition to testing in browsers. For libraries that support both environments.
  2. Testing in Node.js //instead// of web browsers. For libraries primarily meant for browsers that don't actually depend on browser APIs, using Node to easily and quickly run the tests without the overhead of web browsers.

In case 1, you'd expect the source code to have built-in support for module.exports and export there instead of globally. But in either case, using require() to load the code should work fine. The main issue is the test files. We'd want to avoid having to put the following in each test file: var MyLib = typeof require !== undefined ? 'require('../') : MyLib;.

On the other hand, if the alternative trying to specify in a declarative way which modules to load in which variables, that may be even less desirable. Here's a quick sketch:

// tests/.qunitrc.js
module.exports = {
  reporter: 'tap',
  predef: {
    MyLib: require( '../' )
  }
};
# tests/.qunitrc.yml
reporter: tap
predef:
  MyLib: '../'

We can learn from ESLint with regards to supporting arbitrary names as reporter and automatically attempting to find an installed npm module with a matching name/prefix.

@trentmwillis
Copy link
Member Author

trentmwillis commented Mar 16, 2017

Thanks for the review and input on the design!

Do we want to make individual test suites executable (even if just optional)...

Not entirely sure what you mean by this in relation to the rest of your point.

It also has the benefit of allowing test suites to be shared between traditional browser environments and Node.js, since the former would effectively have QUnit predefined as well.

Yes, this is something I have had specific requests for. It may not be as necessary in the future where ES module import/export is supported in all environments, but I think that is a long way off. So for now, I think having it as an implicit global will be fine, especially since we dropped many globals with the 2.x release.

We can learn from ESLint with regards to supporting arbitrary names as reporter and automatically attempting to find an installed npm module with a matching name/prefix.

Yes, Mocha works this way as well, and I think it makes sense. That said, I would like to also support something like a .qunitrc.js file so that if you want a custom reporter, you can just include it as a constructor function directly. I think the predef idea is also good, as I would like to allow users to run tests in both browser and Node without having to add additional logic or build steps into their code.

@Krinkle
Copy link
Member

Krinkle commented Mar 16, 2017

Do we want to make individual test suites executable (even if just optional)...

Not entirely sure what you mean by this in relation to the rest of your point.

I meant the difference between qunit tests/foo.js and node tests/foo.js. The latter is more standalone and is more like how people use assert (as opposed to Mocha), but it would require set up to happen within the file (as part of require( 'qunit' ); and maybe one method call to start the test). This is more flexible for the end-user and makes everything explicit and easy to debug, but it also requires more boilerplate within each test file. It will also probably end up more with a higher learning curve, more back-compat cruft, mistakes, and less flexibility on our side to improve things and take control of output (e.g. instead of relying on the default exception and exit code handling).

With regards to debugging (and other Node.js or V8 command-line flags, like --harmony, which people will want to be able to conveniently set when needed):

# Standalone
node --inspect tests/foo.js

# Custom environment, only works if we explicitly detect and forward it
qunit --node-inspect tests/foo.js

# Manually..
node --inspect ./node_modules/.bin/qunit foo.js

@trentmwillis
Copy link
Member Author

I meant the difference between qunit tests/foo.js and node tests/foo.js.

Ah, okay, I see now. There is nothing to preclude users from setting up their own boilerplate. If this is a feature that winds up being needed/wanted, we could likely enable a mode where we don't do any global injections.

As for the debugging aspect, I agree that the extra overhead is a bit annoying, but it's the same for all Node CLIs that I've used. If you ideas/opinions on the best way to support that, I'm all ears!

@Krinkle
Copy link
Member

Krinkle commented Mar 17, 2017

As for the debugging aspect, I agree that the extra overhead is a bit annoying, but it's the same for all Node CLIs that I've used. If you ideas/opinions on the best way to support that, I'm all ears!

Yeah. Manual wrapping will work for now. And upstream may provide an environment variable in the future to make this easier to trigger in situations where it's not feasible to pass command-line flags directly to the Node process.

bin/utils.js Outdated
let globs = args.slice();

if ( !globs.length ) {
globs.push( "test/*.js" );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion, maybe we should include: test/**/*.js, *.jsx and .mjs (node es modules filename, it's ugly, yes)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good to do the recursive thing.

For the other extensions:

  • Is .mjs officially supported yet?
  • Can you actually require .jsx files? I was under the impression they needed to be compiled first.

bin/utils.js Outdated
} catch ( e ) {}

if ( stat && stat.isDirectory() ) {
return `${glob}/*.js`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

bin/utils.js Outdated
let stat;

try {
stat = fs.statSync( glob );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can explore async + await features?

bin/utils.js Outdated

if ( !files.length ) {
error( "No files were found matching: " + args.join( ", " ) );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not care about it. If files is empty, we should not log errors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think this is useful feedback for users and ensures the process properly exits with a non-zero code in case they accidentally mess up an input path.

@@ -58,13 +68,15 @@
"browserstack": "grunt build && sh build/run-browserstack.sh",
"build": "grunt build",
"test": "grunt",
"coverage": "grunt coverage"
"coverage": "grunt coverage",
"test:cli": "grunt build && npm link && qunit test/cli/*.js"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a noob on npm features, this will call qunit set in the bin property in this same file, here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, by calling npm link we can call qunit as actual users will after installing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trentmwillis You shouldn't need npm link: npm will prepend ./node_modules/.bin to PATH when this npm script is run.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It failed on a previous run. We can mess around with it after this lands, but for now I don't feel it is worth digging into.

test/cli/main.js Outdated

assert.equal( execution.code, 0 );
assert.equal( execution.stderr, "" );
assert.equal( execution.stdout, "TAP version 13\nok 1 1\nok 2 1\n1..2" );
Copy link
Member

@leobalter leobalter Mar 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow (the whole test)

Copy link
Member

@leobalter leobalter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems great for a MVP. I left a few suggestions but they should not block this PR, we can work on follow ups if preferred.

On the walk paths, I remember some work from test262-harness to capture files glob.

@trentmwillis
Copy link
Member Author

I've updated this. I think we'll be good to land once a new version of js-reporters is released with qunitjs/js-reporters#95 included.

I'm not super pleased with the tests being coupled to the output format of the TapReporter but couldn't think of a better way to ensure the correct tests are being run, so if anyone has ideas let me know!

@trentmwillis trentmwillis self-assigned this Mar 19, 2017
@platinumazure
Copy link
Contributor

Could I get a quick explanation of the rationale for setting the exit code equal to the number of test failures?

I ask because I've seen another project (ESLint) make a decision about exit codes and then refusing to change that logic when it otherwise seemed worthwhile due to the possibility of breaking CI integration scripts that depend on exit code values too heavily.

I'm questioning whether there is a lot of value in having the exit code be the number of test failures, when that information should be available in stdout (i.e., in the reporter's output).

I'd much rather use exit codes to actually distinguish between a few different failure scenarios. For example:

  • Exit code 0 means success (obviously)
  • Exit code 1 means tests failed, but no unexpected errors
  • Exit code 2 means an unexpected error occurred that prevented the tests from running to completion
    • We could use more codes to distinguish between these sub-scenarios: Global timeout (all tests combined took too long), CLI args invalid, test file (when specified as a file or directory rather than glob) couldn't be found, error was thrown with notrycatch config enabled, etc.

So personally I'd rather just use codes for specific types of issues/errors and let consumers read the output for further information. But I could definitely be missing something.

@leobalter
Copy link
Member

As in ES specs, it would be nice to have some max-min approach. If we land this only with 1 in the exit, we can defer this discussion on the exit values for later and still have the CLI ready.

I'm in between the two approaches. Returning the number of tests or having some other sort of meaningful error codes. Both looks nice but I tend to prefer the one suggested by @platinumazure. We can already consume the failures but it's nice if QUnit can tell it's an unexpected exit.

Copy link
Contributor

@platinumazure platinumazure left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use tab indentation instead of two-space indentation? This would conform to the jQuery style guide.

@trentmwillis
Copy link
Member Author

Could I get a quick explanation of the rationale for setting the exit code equal to the number of test failures?

Just the first thing I did, no super special reason for it. I'm fine with using specific error codes, so I'll update this.

@trentmwillis trentmwillis changed the title [WIP] CLI: Introduce QUnit CLI CLI: Introduce QUnit CLI Mar 22, 2017
@trentmwillis
Copy link
Member Author

Okay, I've updated with the new js-reporters version and addressed all comments (from what I can tell). @platinumazure would appreciate another review from you 😄

@leobalter
Copy link
Member

I'm amazed to see this work. :shipit: This is one of the most anticipated features for QUnit. :shipit:

@trentmwillis
Copy link
Member Author

@platinumazure sorry to ping again, but if you can't get to it this evening, I'm planning on merging and can address any potential concerns in follow up PRs. Need this one to land so I can do the other features.

@leobalter
Copy link
Member

leobalter commented Mar 24, 2017 via email

Copy link
Contributor

@platinumazure platinumazure left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. LGTM, thanks!

@trentmwillis trentmwillis merged commit c65a374 into qunitjs:master Mar 24, 2017
@trentmwillis trentmwillis mentioned this pull request Mar 25, 2017
@trentmwillis trentmwillis deleted the cli branch March 2, 2018 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants