/* eslint-env mocha */
// Run this specific test using:
// npm test -- -f memory
var http = require('http');
var path = require('path');
var url = require('url');
var fork = require('child_process').fork;

describe('memory usage', function() {
  var cors_api_url;

  var server;
  var cors_anywhere_child;
  before(function(done) {
    server = http.createServer(function(req, res) {
      res.writeHead(200);
      res.end();
    }).listen(0, function() {
      done();
    });
  });

  after(function(done) {
    server.close(function() {
      done();
    });
  });

  beforeEach(function(done) {
    var cors_module_path = path.join(__dirname, 'child');
    var args = [];
    // Uncomment this if you want to compare the performance of CORS Anywhere
    // with the standard no-op http module.
    // args.push('use-http-instead-of-cors-anywhere');
    cors_anywhere_child = fork(cors_module_path, args, {
      execArgv: ['--expose-gc'],
    });
    cors_anywhere_child.once('message', function(cors_url) {
      cors_api_url = cors_url;
      done();
    });
  });

  afterEach(function() {
    cors_anywhere_child.kill();
  });

  /**
   * Perform N CORS Anywhere proxy requests to a simple test server.
   *
   * @param {number} n - number of repetitions.
   * @param {number} requestSize - Approximate size of request in kilobytes.
   * @param {number} memMax - Expected maximum memory usage in kilobytes.
   * @param {function} done - Upon success, called without arguments.
   *   Upon failure, called with the error as parameter.
   */
  function performNRequests(n, requestSize, memMax, done) {
    var remaining = n;
    var request = url.parse(
        cors_api_url + 'http://127.0.0.1:' + server.address().port);
    request.agent = false; // Force Connection: Close
    request.headers = {
      'Long-header': new Array(requestSize * 1e3).join('x'),
    };
    (function requestAgain() {
      if (remaining-- === 0) {
        cors_anywhere_child.once('message', function(memory_usage_delta) {
          console.log('Memory usage delta: ' + memory_usage_delta +
              ' (' + n + ' requests of ' + requestSize + ' kb each)');
          if (memory_usage_delta > memMax * 1e3) {
            // Note: Even if this error is reached, always profile (e.g. using
            // node-inspector) whether it is a true leak, and not e.g. noise
            // caused by the implementation of V8/Node.js.
            // Uncomment args.push('use-http-instead-of-cors-anywhere') at the
            // fork() call to get a sense of what's normal.
            throw new Error('Possible memory leak: ' + memory_usage_delta +
                ' bytes was not released, which exceeds the ' + memMax +
                ' kb limit by ' +
                Math.round(memory_usage_delta / memMax / 10 - 100) + '%.');
          }
          done();
        });
        cors_anywhere_child.send(null);
        return;
      }
      http.request(request, function() {
        requestAgain();
      }).on('error', function(error) {
        done(error);
      }).end();
    })();
  }

  it('100 GET requests 50k', function(done) {
    // This test is just for comparison with the following tests.
    // On Node 0.10.x, the initial memory usage seems higher on Travis, so use
    // a higher maximum value to avoid flaky tests.
    var memMax = /^v0\./.test(process.version) ? 1200 : 550;
    performNRequests(100, 50, memMax, done);
  });

  // 100x 1k and 100x 50k for comparison.
  // Both should use about the same amount of memory if there is no leak.
  it('1000 GET requests 1k', function(done) {
    // Every request should complete within 10ms.
    this.timeout(1000 * 10);
    performNRequests(1000, 1, 2000, done);
  });
  it('1000 GET requests 50k', function(done) {
    // Every request should complete within 10ms.
    this.timeout(1000 * 10);
    performNRequests(1000, 50, 2000, done);
  });
});
