Thursday, September 11, 2014

A Lockfile to Prevent Double NodeJs Generators


The problem: a NPM post-install generator is running when updating packages. I can work around this on test servers—as I found last night, I check to see if the install process is being run from an interactive shell. But, after I implemented and tested that solution, I realized that I likely solved the wrong problem.

The problem is not that my generator is being run under test—which is the problem that I solved. The problem is that my generator is being run after it has already been run.

The point of the package in question, eee-polymer-tests it twofold. First, it generates simple test code and setup for new Polymer elements. Second, it provides the necessary dependencies to run those tests. But really, once the generator has run once, it is no longer of any use (unless I wanted to run it manually to start fresh).

Initially, I had thought to check for the existence of the Spec file. That will not work because neither the test server nor a human performing a simple npm install should have to know the name of the element for which the generator has already been run.

So, in the end, a lockfile seems the best approach. When the generator runs, in addition to creating Karma configuration and Jasmine specs, I will create a dot file in the application. The generator can then cue on that dotfile to know that the generator had already been run.

Once I settle on the approach, the implementation is easy. I define a global to hold the lockfile name:
var DOTFILE = '.eee-polymer-tests-generated';
Then I exit (successfully) if the lockfile exists:
parseCommandLine();

if (fs.existsSync(DOTFILE) && !force) {
  console.log('The spec generator has already been run. Won\'t overwrite without --force.');
  process.exit(0);
}
I already support the --force command-line argument for when this script is run directly from the terminal. If that flag is not present (after parsing the command line) and if the lockfile is present, then I exit gracefully.

And last, I generate the lockfile during the normal generate() process of this generator script:
function generate() {
  ensureTestDirectory();
  if (okKarmaConf()) generateKarmaConf();
  if (okPolymerSetup()) generatePolymerSetup();
  if (okTestSkeleton()) generateTestSkeleton();
  if (okBower()) generateBower();
  generateDotFile();
}
// ...
function generateDotFile() {
  fs.openSync(DOTFILE, 'w');
}
And that is all there is to it. I remove yesterday's interactive terminal check—this lockfile supersedes it. This works from an interactive command-line. A first run generates the lockfile:
npm install ~/repos/eee-polymer-tests
npm WARN package.json parent-events@ No README data
|
> eee-polymer-tests@0.0.1 postinstall /home/chris/repos/polymer-book/book/code-js/parent_child/node_modules/eee-polymer-tests
> ./generator.js

What is the name of the Polymer element being tested? x-parent
Generating test setup for: x-parent
....
$ git status
On branch master
Untracked files:
  (use "git add ..." to include in what will be committed)

        .eee-polymer-tests-generated
And subsequent runs report that there is nothing new to generate:
$ npm install ~/repos/eee-polymer-tests
The spec generator has already been run. Won't overwrite without --force
And it still works on my CI server:
...
[TEST] book/code-js/parent_events 
> eee-polymer-tests@0.0.1 postinstall /var/lib/jenkins/jobs/Patterns in Polymer/workspace/book/code-js/parent_events/node_modules/eee-polymer-tests
> ./generator.js

The spec generator has already been run. Won't overwrite without --force.

Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.052 secs / 0.049 secs)

Success!
Finished: SUCCESS
I am not 100% thrilled with generating an artifact like my lockfile. I think it is the best approach given my requirements. Now to generate a few more tests for Patterns in Polymer!


Day #180

No comments:

Post a Comment