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

Extract tests out of queue into its own testQueue #1260

Merged
merged 8 commits into from
Mar 1, 2018

Conversation

step2yeung
Copy link
Contributor

This PR separates the content of Qunit.config.queue which contains both tests (runTest function) and its tasks(each function added to the queue by runTest) into two separate queue: queue and testQueue.

Previously the queue was serving two purposes:

  1. tracking the list of tests to run
  2. when a given test is ran it unshifts that specific tests tasks to the front of the queue

This PR also goes along the lines of the first comment by platinumazure in this PR: #1124
The idea of this change is to clean up and simplify the queue logic to make it easier to reason about. The change separates the processing of tests and its tasks, giving a clear indication when all the tasks of a tests are complete, and when the next test can begin.

Before:

QUnit.config.queue = [
  function() { test.before() },
  function runHook(), # before
  function() { test.preserveTestEnvironment(); },
  function runHook(), #beforeEach
  function() { test.run(); },
  function runHook(), #afterEach
  function runHook(), # after
  function() { test.after(); },
  function() { test.finish(); },
  function runTest(),
  function runTest(),
  function runTest(),
  # ...
];

After:

QUnit.config.queue = [
  function() { test.before() },
  function runHook(), # before
  function() { test.preserveTestEnvironment(); },
  function runHook(), #beforeEach
  function() { test.run(); },
  function runHook(), #afterEach
  function runHook(), # after
  function() { test.after(); },
  function() { test.finish(); },
];

QUnit.config.testQueue = [
  function runTest(),
  function runTest(),
  function runTest(),
  # ...
];

@jsf-clabot
Copy link

jsf-clabot commented Feb 1, 2018

CLA assistant check
Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Stephen Yeung seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.

const start = now();
config.depth = ( config.depth || 0 ) + 1;

while ( config.queue.length && !config.blocking ) {
const elapsedTime = now() - start;

if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
if ( priorityCount > 0 ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this code unneeded? What was its original purpose? Can you explain why we don't need it now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the original code, priorityCount's main purpose is to prioritize tests that was flagged to be executed first. The priorityCount was also incremented/decremented in advance() originally because if a tests needed to be inserted to the queue, it will be inserted after the tasks(which gets added to front of the queue) and after the already prioritized tests.
This is not necessary anymore since the task is in it's own queue, so we do not need to deal with the priortyCount when dealing with the tasks. (Only needed for the testQueue in the new implementation)

function addToTaskQueue( tasksArray ) {
for ( let i = 0; i < tasksArray.length; i++ ) {
const taskItem = tasksArray[ i ];
config.queue.push( taskItem );
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this whole function can be simplified to:

config.queue.push(...tasksArray)

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it certainly can. Will update that.

Copy link
Member

@trentmwillis trentmwillis left a comment

Choose a reason for hiding this comment

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

I haven't reviewed the changes yet, but I'm curious (as it isn't clear from the PR description) as to what the goal is with this change?

We're introducing another field into QUnit.config, but I'm not clear on what the benefit of doing so is for consumers. Is there a use case you have in mind for it?

@rwjblue
Copy link
Contributor

rwjblue commented Feb 9, 2018

I don’t think the intent is really related to exposing another queue, just trying to simply the queueing logic.

In the long run, having separate queues for the list of tasks for a single test and the pending tests will make it easier to add more logic around test to test advancing (e.g. so that measurements can be done to prevent memory leaks and/or identify per-test code coverage), but that is definitely not the goal of this PR itself. This PR is only about simplifying the queuing logic (having a single queue where some items are prepended and others are appended is really confusing).

@rwjblue
Copy link
Contributor

rwjblue commented Feb 9, 2018

If you’d prefer to avoid adding a new property on QUnit.config I think we can most likely do that.

@step2yeung - Can you confirm?

@step2yeung
Copy link
Contributor Author

Thanks for clarifying @rwjblue!
We can avoid adding it to QUnit.config, and just have the testQueue be private to processing-queue.js.
If thats the case, then is it even necessary to expose the QUnit.config.queue?

@trentmwillis
Copy link
Member

Yeah, my concern is primarily around adding a new public property. If we can keep testQueue internal, then I'm 👍 on this. We'll still need QUnit.config.queue because that is how the CLI/HTML Reporter abort/cancel test runs. We may want to add a proper public API for that behavior, but that should be a separate PR.

@step2yeung
Copy link
Contributor Author

step2yeung commented Feb 9, 2018

@trentmwillis Thanks for the feedback! I modified it such that config.queue contains the list of tests, where as taskQueue is now private to ProcessingQueue. This way, the understanding of QUnit.config.queue stays relatively the same when it comes to test run aborting/canceling, and we minimize the changes to two files.

Copy link
Contributor

@rwjblue rwjblue left a comment

Choose a reason for hiding this comment

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

This is looking really good to me, thanks for working on this cleanup @step2yeung!

@trentmwillis - Have any time for a follow-up review?

src/test.js Outdated
@@ -205,7 +205,7 @@ Test.prototype = {

if ( hookName === "after" &&
hookOwner.unskippedTestsRun !== numberOfUnskippedTests( hookOwner ) - 1 &&
config.queue.length > 2 ) {
( config.queue.length > 0 || ProcessingQueue.taskCount() > 2 ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not really related to this PR since it was pre-exixting, but this took me a little while to figure out (specifically, why is > 2 important), it may warrant an inline comment...

Copy link
Member

Choose a reason for hiding this comment

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

Agreed...should've done that last time I refactored this code

Copy link
Member

@trentmwillis trentmwillis left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! Sorry for being slow to review. Overall, the implementation seems pretty good. Mainly have some suggestions around comments/clarification for future travelers.

@@ -21,69 +20,91 @@ import {

let priorityCount = 0;
let unitSampler;
const taskQueue = [];
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to have a comment describing what a task is within the taskQueue. Something to the effect of:

This is queue of functions that are tasks within a single test. After tests and removed from config.queue they are expanded into a set of tasks in this queue.

* Adds a function to the ProcessingQueue for execution.
* @param {Function|Array} callback
* @param {Boolean} priority
* Adds a tests to the TestQueue for execution.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Should be singular, test.

src/test.js Outdated
@@ -205,7 +205,7 @@ Test.prototype = {

if ( hookName === "after" &&
hookOwner.unskippedTestsRun !== numberOfUnskippedTests( hookOwner ) - 1 &&
config.queue.length > 2 ) {
( config.queue.length > 0 || ProcessingQueue.taskCount() > 2 ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Agreed...should've done that last time I refactored this code

* @param {Function|Array} callback
* @param {Boolean} priority
* Adds a tests to the TestQueue for execution.
* @param {Array} testTasksArray
Copy link
Member

Choose a reason for hiding this comment

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

I could be wrong, but isn't this always going to be a function now? We pass the runTest function into here, and then later shift them off and then pass the expanded array to addToTaskQueue, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct! I renamed this to testTasksFunc

const elapsedTime = now() - start;

if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
if ( priorityCount > 0 ) {
priorityCount--;
Copy link
Member

Choose a reason for hiding this comment

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

I believe we need to reintroduce this priorityCount decrement somewhere so that if a high priority test is added after the run starts, it should be placed in the right spot. I doubt we have a proper test for this, so we could always add that back with a test in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO for that.

Copy link
Member

@trentmwillis trentmwillis left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks a bunch!

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