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

NodeJS: Do not execute all rulesets #15

Open
lasar opened this issue Nov 18, 2013 · 2 comments
Open

NodeJS: Do not execute all rulesets #15

lasar opened this issue Nov 18, 2013 · 2 comments

Comments

@lasar
Copy link

lasar commented Nov 18, 2013

We've partially talked about this on ADN. To recap:

I want to be able to write a sheet file where every ruleset is meant for a specific URL. To avoid writing URLs twice I want to parse the URL from the ruleset's conditions.
When that's done I want to loop through all rulesets. For each ruleset I would fetch the desired URL and then run the one ruleset over it.

My sheet file looks like this:

@page (url = "http://www.example.com/") {
    status-code: 200;
    #pageTitle h1 { text: /Welcome/; }
}

@page (url = "http://www.example.de/") {
    status-code: 302;
}
@page (url = "http://www.example.de/de/") {
    status-code: 200;
    #pageTitle h1 { text: /Willkommen/; }
}

What I have so far: First I load a sheet and use rules.forEach to get each one (simplified, my actual code includes a little error checking):

var bas = new BAS();
var suite = bas.loadSheet(sheet);
suite.yep(function() {
    var rules = [];
    bas.rules.forEach(function(rule) {
        var url = myGetRuleUrl(rule);
        rules.push({url: url, rule: rule});
    });
    myCallback(rules);
});

The function myGetRuleUrl parses the rule.input and extracts the URL from the ruleset's conditions (it understands url, protocol, domain and path). In my use case I only use simple strings for comparison. Regular expressions would not work for what I have in mind.

The myCallback function then loops through each result and does this (again, simplified for readability):

var testSuite = new BAS();
testSuite.loadSheet(sheet); // Same sheet from before
 request(url, function(err, res, body) {
    testSuite.run(url, res, body);
testSuite.on('end', function() {
        // Result is handled here
    });
});

So essentially I'm setting up a new Bas instance for every URL, and then I run the entire sheet over it, even though I already know that only one of the rulesets is going to match.
Perhaps it is silly to worry over this since Bas is probably pretty quick about evaluating conditions on all the rulesets. But it's not elegant :)

I can think of two ways to simplify this.

First, add a method to add a rule object (as returned by BAS.rules.forEach) to a test suite:

var testSuite = new BAS();
testSuite.addRule(rule);

Second option: Convert a rule object back into its string representation, so I can pass it back into BAS.loadSheet:

var ruleString = rule.toString();
var testSuite = new BAS();
testSuite.loadSheet(ruleString);

I hope it's clear what I'm trying to accomplish.

But perhaps I'm approaching this from the wrong angle. I'm currently doing complicated things only to avoid writing the same URL twice.

Maybe I would be better off inventing a meta-syntax (or simple two-column database) for URL => ruleset. Then each ruleset could simply be of the @all type and I would have no problem running each test separately.

@cgiffard
Copy link
Owner

I think it's reasonable to want to run top-level rulesets on a discretionary basis. I could supply a run method on the Rulesets (each one of bas.rules) that would take the same arguments as Bas.run. Would that suit your needs?

In regards to a meta-syntax: have you looked into Bas' annotation system? You could do something like this in your sheet:

/*@url:http://example.com/abc?def=123*/
@all {
    status-code: 200;
}

/*@url:http://example2.net/ghi?klm=456*/
@all {
    status-code: 302;
}

And then in your core code you could extract the URLs like so:

testSuite.rules.forEach(function(rule) {
    var urls = 
        rule.annotations
            .filter(function(annotation) {
                return annotation.match(/^\s*url\:/i);
            })
            .map(function(annotation) {
                return (
                    annotation
                        .replace(/(^\s+|\s+$)/g)
                        .substr(4)
                );
            });

    urls.forEach(function(url) {
        request(url,function(err, res, body) {
            if (err) { /* handle err */ }

            rule.run(url, res, body);
        });
    });
});

(Please note, I haven't tested that, and it depends on the currently fictitious rule.run method, but it serves to demonstrate how I would approach the problem.)


As a side note, please be aware that Bas.loadSheet is an async operation (it uses an async fs call.) This means that if you want to avoid race conditions, you'll need to supply a node-style callback or handle the returned promise like so:

testSuite.loadSheet("example.bas")
    .yep(function() { /* handle success */ })
    .nope(function() { /* handle failure */ });

@lasar
Copy link
Author

lasar commented Nov 18, 2013

rule.run would fit perfectly. Your example code could replace most of the code I wrote around Bas :)

I hadn't looked at annotations. As mentioned I parse the URL from @page (url = "http://example.com"), which works similarly well for me. But I think your example code is neater. Plus, your code could be expanded so that I could have multiple URLs use the same ruleset.

As for loadSheet, I simply forgot to use yep/nope in one place. Thanks for the reminder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants