-->

Continue certain tasks in grunt even if one fails

2019-02-07 23:05发布

问题:

Is there a way to configure a sequence of tasks so that specific subsequent ones (I don't want --force on the whole batch) run even if one fails? For example, consider a case like this

  1. Create some temporary files
  2. Run some unit tests which involve those temporary files
  3. Clean up those temporary files

I can do this:

grunt.registerTask('testTheTemp', ['makeTempFiles', 'qunit', 'removeTempFiles']);

But if qunit fails then the removeTempFiles task never runs.

回答1:

Here's one workaround. It's not pretty, but it does solve the issue.

You create two extra tasks which you can wrap at the beginning/end of any sequence that you want to continue even over failure. The check for existing value of grunt.option('force') is so that you do not overwrite any --force passed from the command line.

grunt.registerTask('usetheforce_on',
 'force the force option on if needed', 
 function() {
  if ( !grunt.option( 'force' ) ) {
    grunt.config.set('usetheforce_set', true);
    grunt.option( 'force', true );
  }
});
grunt.registerTask('usetheforce_restore', 
  'turn force option off if we have previously set it', 
  function() {
  if ( grunt.config.get('usetheforce_set') ) {
    grunt.option( 'force', false );
  }
});
grunt.registerTask( 'myspecialsequence',  [
  'usetheforce_on', 
  'task_that_might_fail_and_we_do_not_care', 
  'another_task', 
  'usetheforce_restore', 
  'qunit', 
  'task_that_should_not_run_after_failed_unit_tests'
] );

I've also submitted a feature request for Grunt to support this natively.



回答2:

For posterity sake, this might be an improved hack while we wait for that PR from @explunit to land in grunt:

var previous_force_state = grunt.option("force");

grunt.registerTask("force",function(set){
    if (set === "on") {
        grunt.option("force",true);
    }
    else if (set === "off") {
        grunt.option("force",false);
    }
    else if (set === "restore") {
        grunt.option("force",previous_force_state);
    }
});

// .....

grunt.registerTask("foobar",[
    "task1",
    "task2",
    "force:on",     // temporarily turn on --force
    "task3",        // will run with --force in effect
    "force:restore",// restore previous --force state
    "task4"
]);


回答3:

Perhaps you can create an async grunt task and grunt.util.spawn your desired tasks serially. You can then write some conditional logic for the success/error codes. Something similar to the answer to this question



回答4:

Echoing Marius' comment, the grunt-force-task plugin now provides this functionality. Full details by following the link above, but in a nutshell this is what you need to achieve the desired effect

npm install grunt-force-task --save-dev

Then import it into your gruntfile

grunt.loadNpmTasks('grunt-force-task');

Finally, just add the force: prefix to the task(s) before the one you always want to run.

grunt.registerTask('testTemp', ['makeTempFiles', 'force:qunit', 'removeTempFiles']);

Now removeTempFiles will always run even if the test fails.



回答5:

The one issue with using the grunt-force-task plugin mentioned above is that the grunt process will now unconditionally exit with 0 (which means pass).

This is an issue if you want to use grunt in a CI (continuous integration) environment and fail the CI task based on whether your test/build (qunit in the OP) passes or fails. I have worked around this issue by adding a new task that uses grunt's this.requires function to test whether qunit passed or failed:

grunt.registerTask('exitWithQunitStatus', function() {
  this.requires(['qunit']);
  return true;
})

grunt.registerTask('testTheTemp', ['makeTempFiles', 'force:qunit', 'removeTempFiles', 'exitWithQunitStatus']);

Now if qunit fails grunt will exit with 3, which indicates Task Error. Without the exitWithQunitStatus, the grunt process will exit with 0.

this.requires is documented here : http://gruntjs.com/api/inside-tasks#this.requires . Basically it will fail the current task unless all of the specified "required" tasks have already run and passed.