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

Simulate external servers with Node.js+Javascript instead of (Apache|mongoose|…)+PHP #1999

Closed
gibson042 opened this issue Jan 8, 2015 · 9 comments · Fixed by GulajavaMinistudio/jquery#33
Assignees
Labels
Milestone

Comments

@gibson042
Copy link
Member

This will allow us to set up and tear down the server with grunt and track all dependencies in package.json, but requires reimplementing our PHP scripts and probably (hopefully?) also establishing a mechanism for communicating the listening port to QUnit.

@markelog
Copy link
Member

markelog commented Jan 8, 2015

There is complication about that - testswarm requires ability to execute tests on any previous commit not just the current one. Otherwise it would done a long time ago.

@Krinkle have more info about that.

@markelog
Copy link
Member

markelog commented Jan 8, 2015

Or just move away from testswarm, as it was suggested by the infra team, browserstack-tunnel or karma our candidates for the replacement.

But those requires some good amount of time to do it, at least karma does, which is my preference.

@ChrisAntaki
Copy link
Contributor

Intern looks like an option as well jquery/testswarm@dd6002f

@markelog
Copy link
Member

markelog commented Jan 8, 2015

Does intern offers qunit support? I was under impression it has its own framework.

/cc @csnover

@csnover
Copy link
Member

csnover commented Jan 8, 2015

You could certainly load QUnit into it if you wanted (I have heard of some people dumping Mocha in there), but it’s a kind of inception if you do since it comes with quite robust unit testing built-in :)

I’ve actually got a mostly-working branch with a full* QUnit API compatible interface so, with only a small amount of work to address the TODOs (probably only a day or two of work?), you should be able to drop existing QUnit tests into Intern by wrapping them with define([ 'intern!qunit'], function (QUnit) { }) and have them Just Work.

(And then, fingers crossed!, I can convince team QUnit to come over & help me get cool new stuff into Intern more quickly :)))

* Well it was, prior to the most recent QUnit release. Now it is slightly outdated, but it should be easy to update, since from what I could see the QUnit update was adding some features that Intern already has so it’s easy to map them

@Krinkle
Copy link
Member

Krinkle commented Jan 8, 2015

Brain dump:

Assumption: This dynamic server would be auto-started by Grunt, and uses automatic port choosing in order to run concurrently with other services on the same computer (e.g. projects developers run locally, as well as concurrent builds in Jenkins). Alternatively, we'd block this on migrating to Travis CI (no port clash) and/or pick unique ports for each git branch in each git repo.


As long as TestSwarm is used (unless you're okay with ajax tests not running in TestSwarm), we can't have entry points not automatically served by common installations of Apache and nginx (hence PHP was used).

Any server set up by Grunt won't exist by the time TestSwarm distributes the test urls to BrowserStack. These test urls point at the not-so-static "static" web server serving build artefacts from Jenkins jobs for upto 30 days at unique subdirectories (html/js/css and php, served by nginx). It can also be re-ran from TestSwarm at any point in the future (within 30 days).

Even if we'd remove the re-run feature in TestSwarm, QUnit would still have to know about the hostname and port the server is on. And the url would have to be publicly exposed to BrowserStack. Though of course, this wouldn't be an issue if it was using an ssh tunnel to BrowserStack. But I'm not sure that is feasible in the current testswarm-browserstack setup.

@gibson042
Copy link
Member Author

For the purposes of CI generally (as opposed to local development) and TestSwarm in particular, I imagine we'd still use the stable servers for builds.jenkins.jquery.com/jquery/{build}/… URLs, they'd just defer to grunt-managed node.js servers for dynamic responses. There are many ways to skin this cat, but here's one example:

  • On inbound request to a build URL, the PHP handler verifies that the corresponding port is known
    • ...and if not, spawns the build's grunt test-server and then checks output after a little while to discover it (with suitable failure handling, perhaps kill the spawned process and respond 404)
  • PHP gets its responses from forwarding the request to that port (with suitable failure-to-connect handling, perhaps decrement available tries and replay the original request)
  • The test-server grunt task leverages grunt-contrib-connect and possibly also node-proper-lockfile, something like:
// Connect to already-running server listening at port specified in the runfile
tryConnecting();
function tryConnecting() {
  try {
    port = fs.readFileSync( runfile );
    req = http.get({ port: port || "invalid" }, function() {
      console.log( "Server running at port " + port );
    });

    // On failure, try to _start_ a server
    req.on( "error", tryStarting );
  } catch ( err ) {
    tryStarting( err );
  }
}

function tryStarting( err ) {
  console.warn( err.message );

  // Lock the runfile for our use
  lockfile.lock(
    runfile,
    {
      // Consider existing locks stale after 7 seconds
      stale: 7000,

      // Try acquisition for up to 7 seconds (2x backoff per retry)
      { retries: 3, minTimeout: 1000 }
    },
    function( err, release ) {
      // Got the lock
      if ( !err ) {
        var keepAlive = grunt.config( "connect.testServer.keepAlive" ),
          onCreate = grunt.config( "connect.testServer.onCreateServer" );

        // Server creation listener
        onCreate.push(function( server, connect, options ) {
          console.log( "Server running at port " + options.port );

          // Write out the runfile and register a shutdown listener to clear it
          fs.writeFileSync( runfile, options.port + "" );
          server.on( "close", function() {
            fs.writeFile( runfile, "" );
            release();
          });

          // Schedule a deferable future shutdown
          if ( keepAlive ) {
            grunt.config( "connect.testServer.shutdown" ) =
              setTimeout( server.close.bind( server ), keepAlive );
          }
        });

        // Start the server daemon (only self-terminating by the above)
        grunt.task.run( "connect:testServer:keepalive" );

      // If we lost a race for the lock, try connecting to the winner
      } else if ( err.code === "ELOCKED" ) {
        console.warn( "Couldn't get lock on " + runfile );
        tryConnecting();

      // Bail on any other error
      } else {
        throw err;
      }
    }
  );
}

@csnover
Copy link
Member

csnover commented Jan 9, 2015

Since I was summoned already and so am seeing replies on this, I would just also point out that Dojo core has a JSGI server for mock services set up with its Intern test suite, so you don’t need to do much work again, you could just take that code and adapt it for your own use. It’s used to do the same thing the jQuery PHP test scripts are used for (testing Dojo’s I/O code). The services are in the tests/services directory.

@gibson042 gibson042 changed the title Simulate external servers with node.js+Javascript instead of (Apache|mongoose|…)+PHP Simulate external servers with Node.js+Javascript instead of (Apache|mongoose|…)+PHP Jan 12, 2015
@timmywil timmywil added this to the Future milestone Jan 15, 2016
@Krinkle
Copy link
Member

Krinkle commented Jul 31, 2017

Regarding incompatibility between TestSwarm (which independently and outside the Grunt/Jenkins run context) and Travis CI / local development, we can use the isSwarm variable that already exists in jQuery core unit tests to make any decisions that need to vary. (E.g. to change the url paths slightly, in case we don't want the .php suffix for the Node.js version of these endpoints)

More likely though, from what I can tell there isn't any need for the urls to vary. We could easily implement the same urls both ways.

The Ajax tests are already written with the expectation that they are served over HTTP (e.g. any static server will work, and if served over file:/// the Ajax tests are already skipped). The tests make requests to the PHP files using relative urls.

We'd configure out Node.js server to similarly serve all files statically, with a non-static router (that has higher precedence) for our Ajax server mocks. E.g. route /data/ajax/:script.php to a JavaScript based handler that will vary according to the script name and query parameters accordingly.

Once that is fixed, we can run the unit tests normally with QUnit/Karma/Grunt, e.g. defaulting to Headless Chrome, and we can run them Travis CI in at least Headless Chrome and Firefox (pre-installed). In addition, once they pass in e.g. Headless Chrome, we can go run them in various other browsers using Saucelabs or BrowserStack as various other jQuery projects do already.

All this can happen whilst the tests will continue to work in TestSwarm as well. (If desired)

Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 1, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Base URI
  - Karma opens at /context.html and serves test/ files from /base/test/.
    This is incompatible with our manual run which is at /test/index.html.
  - Using a <base href> tag with "/base/test/" fixed most tests when trying
    it out, but still caused three problems:
    1. While <base href> fixes native expansion (e.g. in HTML attributes,
       in CSS, and when passed to XHR), it did affect in-JS expansion in
       our tests, which use hacks based on location.href (e.g. for <a>
       property tests in unit/basic.js, and creation of absolute urls
       by unit/ajax.js).
       Fix those by introducing a prependUrlBase() function that prepends
       document.baseURI instead of location.href
    2. A test in unit/manipulation.js clicks on an anchor link. This broke
       the test entirely by navigating away from the test page since href="jquery#2"
       would now expand to /base/test/jquery#2.
       Fix those by:
       - Remove the <base> tag again.
       - Revert the logic in prependUrlBase() to how it used to be inlined.
       - Remove prependUrlBase() from basic/attributes test.
       - Instead, fix url() to have baseURL be "/base/test/" when in Karma.
       - Apply url() to bare urls:
         - unit/core.js (globalEval test)
         - unit/support.js (csp test)

* In addition to all the above, the most time-consuming part still was making
  the Ajax tests pass because they currently assume being served from
  Apache with mod_php installed.
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 1, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Base URI
  - Karma opens at /context.html and serves test/ files from /base/test/.
    This is incompatible with our manual run which is at /test/index.html.
  - Using a <base href> tag with "/base/test/" fixed most tests when trying
    it out, but still caused three problems:
    1. While <base href> fixes native expansion (e.g. in HTML attributes,
       in CSS, and when passed to XHR), it did affect in-JS expansion in
       our tests, which use hacks based on location.href (e.g. for <a>
       property tests in unit/basic.js, and creation of absolute urls
       by unit/ajax.js).
       Fix those by introducing a prependUrlBase() function that prepends
       document.baseURI instead of location.href
    2. A test in unit/manipulation.js clicks on an anchor link. This broke
       the test entirely by navigating away from the test page since href="jquery#2"
       would now expand to /base/test/jquery#2.
       Fix those by:
       - Remove the <base> tag again.
       - Revert the logic in prependUrlBase() to how it used to be inlined.
       - Remove prependUrlBase() from basic/attributes test.
       - Instead, fix url() to have baseURL be "/base/test/" when in Karma.
       - Apply url() to bare urls:
         - unit/core.js (globalEval test)
         - unit/support.js (csp test)

* In addition to all the above, the most time-consuming part still was making
  the Ajax tests pass because they currently assume being served from
  Apache with mod_php installed.
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 1, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Base URI
  - Karma opens at /context.html and serves test/ files from /base/test/.
    This is incompatible with our manual run which is at /test/index.html.
  - Using a <base href> tag with "/base/test/" fixed most tests when trying
    it out, but still caused three problems:
    1. While <base href> fixes native expansion (e.g. in HTML attributes,
       in CSS, and when passed to XHR), it did affect in-JS expansion in
       our tests, which use hacks based on location.href (e.g. for <a>
       property tests in unit/basic.js, and creation of absolute urls
       by unit/ajax.js).
       Fix those by introducing a prependUrlBase() function that prepends
       document.baseURI instead of location.href
    2. A test in unit/manipulation.js clicks on an anchor link. This broke
       the test entirely by navigating away from the test page since href="jquery#2"
       would now expand to /base/test/jquery#2.
       Fix those by:
       - Remove the <base> tag again.
       - Revert the logic in prependUrlBase() to how it used to be inlined.
       - Remove prependUrlBase() from basic/attributes test.
       - Instead, fix url() to have baseURL be "/base/test/" when in Karma.
       - Apply url() to bare urls:
         - unit/core.js (globalEval test)
         - unit/support.js (csp test)

* In addition to all the above, the most time-consuming part still was making
  the Ajax tests pass because they currently assume being served from
  Apache with mod_php installed.
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 1, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Base URI
  - Karma opens at /context.html and serves test/ files from /base/test/.
    This is incompatible with our manual run which is at /test/index.html.
  - Using a <base href> tag with "/base/test/" fixed most tests when trying
    it out, but still caused three problems:
    1. While <base href> fixes native expansion (e.g. in HTML attributes,
       in CSS, and when passed to XHR), it did affect in-JS expansion in
       our tests, which use hacks based on location.href (e.g. for <a>
       property tests in unit/basic.js, and creation of absolute urls
       by unit/ajax.js).
       Fix those by introducing a prependUrlBase() function that prepends
       document.baseURI instead of location.href
    2. A test in unit/manipulation.js clicks on an anchor link. This broke
       the test entirely by navigating away from the test page since href="jquery#2"
       would now expand to /base/test/jquery#2.
       Fix those by:
       - Remove the <base> tag again.
       - Revert the logic in prependUrlBase() to how it used to be inlined.
       - Remove prependUrlBase() from basic/attributes test.
       - Instead, fix url() to have baseURL be "/base/test/" when in Karma.
       - Apply url() to bare urls:
         - unit/core.js (globalEval test)
         - unit/support.js (csp test)

* Move definition of 'isSwarm' and 'basicTests' out of loadTests(), given
  Karma will run without that function.

* In addition to all the above, the most time-consuming part still was making
  the Ajax tests pass because they currently assume being served from
  Apache with mod_php installed.
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 2, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Move definition of 'isSwarm' and 'basicTests' out of loadTests(), given
  Karma will run without that function.

* In addition to all the above, the most time-consuming part still was making
  the Ajax tests pass because they currently assume being served from
  Apache with mod_php installed.
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 2, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Move definition of 'isSwarm' and 'basicTests' out of loadTests(), given
  Karma will run without that function.

* In addition to all the above, start porting some of the PHP mocks to Node.js.
  (The Ajax tests currently assume being served from Apache with mod_php.).
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Ref issue jquerygh-1999, pull jquerygh-3744.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 2, 2017
* Merge all PHP stubs into one MockServer class.
* Complete the middleware-mockserver.js port (Node.js equivalent).

Notes:
* Merge action=script, and action=evalScript. (Similar)
* Merge action=wait, action=longLoadScript, action=dont_return. (Similar)
* Merge action=status, and action=nocontent. (Similar)
* Renamed text.php to text.txt. (No PHP code)
* Change 404.php reference to 404.txt. (Neither exists)
* Remove data/test.php. (Unused)

Now that all tests pass, also add 'grunt karma:main' to npm test.

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 2, 2017
* Merge all PHP stubs into one MockServer class.
* Complete the middleware-mockserver.js port (Node.js equivalent).

Notes:
* Merge action=script, and action=evalScript. (Similar)
* Merge action=wait, action=longLoadScript, action=dont_return. (Similar)
* Merge action=status, and action=nocontent. (Similar)
* Renamed text.php to text.txt. (No PHP code)
* Change 404.php reference to 404.txt. (Neither exists)
* Remove data/test.php. (Unused)

Now that all tests pass, also add 'grunt karma:main' to npm test (for use
in local development and on Travis CI).

Also add a separate 'npm run jenkins' for use on jenkins.jquery.com where
we won't run Chrome (runs TestSwarm instead).

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 18, 2017
* Add 'grunt karma:main' task that runs tests via Karma.
  - Karma uses its own server and uses its own HTML page by default.
    All files to be served are registered, and for the most part also
    loaded ("included") through Karma.
  - Use the 'customContextFile' option, because:
    * Preserve the qunit-fixture we have.
    * Workaround karma-qunit bug.
  - Use the 'customDebugFile' option, because (above reasons) and:
    * Create 'qunit' element and load QUnit CSS styles for easier debugging.

* Move definition of 'isSwarm' and 'basicTests' out of loadTests(), given
  Karma will run without that function.

* In addition to all the above, start porting some of the PHP mocks to Node.js.
  (The Ajax tests currently assume being served from Apache with mod_php.).
  I originally planned on addressing this by starting another Node server
  before Karma starts, somehow injecting its url, and stopping it after Karma.
  Instead, let's try to use Karma's support for middleware to (hopefully)
  make this prettier.

Ref issue jquerygh-1999, pull jquerygh-3744.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 18, 2017
* Merge all PHP stubs into one MockServer class.
* Complete the middleware-mockserver.js port (Node.js equivalent).

Notes:
* Merge action=script, and action=evalScript. (Similar)
* Merge action=wait, action=longLoadScript, action=dont_return. (Similar)
* Merge action=status, and action=nocontent. (Similar)
* Renamed text.php to text.txt. (No PHP code)
* Change 404.php reference to 404.txt. (Neither exists)
* Remove data/test.php. (Unused)

Now that all tests pass, also add 'grunt karma:main' to npm test (for use
in local development and on Travis CI).

Also add a separate 'npm run jenkins' for use on jenkins.jquery.com where
we won't run Chrome (runs TestSwarm instead).

Fixes jquery#1999.
Krinkle added a commit to Krinkle/jquery that referenced this issue Aug 18, 2017
* Merge all PHP stubs into one MockServer class.
* Complete the middleware-mockserver.js port (Node.js equivalent).

Notes:
* Merge action=script, and action=evalScript. (Similar)
* Merge action=wait, action=longLoadScript, action=dont_return. (Similar)
* Merge action=status, and action=nocontent. (Similar)
* Renamed text.php to text.txt. (No PHP code)
* Merge references to 404.php, 404.html, and someFileThatDoesNotExist.html
  to 404.txt, and add a handler for the latter to test middleware
  to avoid warnings in the Karma server output.
* Remove data/test.php. (Unused)

Now that all tests pass, also add 'grunt karma:main' to npm test (for use
in local development and on Travis CI).

Also add a separate 'npm run jenkins' for use on jenkins.jquery.com where
we won't run Chrome (runs TestSwarm instead).

Fixes jquery#1999.
@timmywil timmywil modified the milestones: Future, 3.3.0 Dec 18, 2017
@lock lock bot locked as resolved and limited conversation to collaborators Jun 17, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.