Improve rtcbot to load all test files at start and allow them to registerTests
via: registerBotTest. After loading all tests main.js starts running the
requested one on the command arguments.

R=houssainy@google.com

Review URL: https://webrtc-codereview.appspot.com/29779004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7461 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/tools/rtcbot/README b/webrtc/tools/rtcbot/README
index 72f9f63..bfa981c 100644
--- a/webrtc/tools/rtcbot/README
+++ b/webrtc/tools/rtcbot/README
@@ -17,7 +17,10 @@
 == How to run the test ==
  $ cd trunk/webrtc/tool/rtcbot
  $ npm install express browserify ws websocket-stream dnode
- $ node test.js <test_file_path>
+ $ node main.js "<test_name>"
+
+== How can I see the list of available tests? ==
+ $ node main.js
 
 == Example on how to install nodejs ==
  $ cd /work/tools/
@@ -30,7 +33,7 @@
   - "chrome": chrome on host machine.
   - "android-chrome": chrome on android device. Details in "Android" Section.
 
- * Bot type is specified for each spawned bot in the test file.
+ * Bot type is specified directly by the test.
 
 == Android ==
 Before running test with Android one MUST forward the device port 8080 to the
diff --git a/webrtc/tools/rtcbot/main.js b/webrtc/tools/rtcbot/main.js
new file mode 100644
index 0000000..665f6e8
--- /dev/null
+++ b/webrtc/tools/rtcbot/main.js
@@ -0,0 +1,102 @@
+// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+//
+// This script loads all the test/* files into a very small context that
+// only exposes a minimal set of functions that allows to register tests.
+//
+// Once all files are loaded it runs the specific test on the command line.
+// If no arguments are given it lists all the registered tests.
+//
+// Note: the small context where the scripts are loaded is intended to keep
+// nodejs-isms away from the test code and isolate implementation details away
+// from them.
+var fs = require('fs');
+var vm = require('vm');
+var Test = require('./test.js');
+
+var testSuites = {};
+
+function registerTest(name, func) {
+  testSuites[name] = func;
+}
+
+function registerBotTest(name, func, bots) {
+  registerTest(name, bootstrap);
+
+  function bootstrap(test) {
+    var callbacks = [];
+    for (var i = 0; i != bots.length; ++i)
+      callbacks.push(test.spawnBot.bind(test, "", bots[i]));
+
+    test.wait(callbacks, func.bind(test, test));
+  }
+}
+
+function loadTestFile(filename, doneCallback) {
+  var loadTestContext = {
+    setTimeout: setTimeout,
+    registerTest: registerTest,
+    registerBotTest: registerBotTest
+  };
+  var script = vm.createScript(fs.readFileSync(filename), filename);
+  script.runInNewContext(loadTestContext);
+  doneCallback();
+}
+
+function iterateOverTestFiles(foreachCallback, doneCallback) {
+  fs.readdir('test', function (error, list) {
+    function iterateNextFile() {
+      if (list.length === 0) {
+        doneCallback();
+      } else {
+        var filename = list.pop();
+        if (filename[0] === '.' || filename.slice(-3) !== '.js') {
+          // Skip hidden and non .js files on that directory.
+          iterateNextFile();
+        } else {
+          foreachCallback('test/' + filename, iterateNextFile);
+        }
+      }
+    }
+
+    if (error !== null) {
+      throw error;
+    }
+    iterateNextFile();
+  });
+}
+
+function runTest(testname) {
+  if (testname in testSuites) {
+    console.log("Running test: " + testname);
+    var test = new Test();
+    testSuites[testname](test);
+  } else {
+    console.log("Unknown test: " + testname);
+  }
+}
+
+function printUsage() {
+  console.log('Run as:\n $ '
+      + process.argv[0] + ' ' + process.argv[1]
+      + ' <testname>');
+  console.log('These are the existent ones:');
+  for (var testname in testSuites)
+    console.log('  ' + testname);
+}
+
+function main() {
+  // TODO(andresp): support multiple tests.
+  var testList = process.argv.slice(2);
+  if (testList.length === 1)
+    runTest(testList[0]);
+  else
+    printUsage();
+}
+
+iterateOverTestFiles(loadTestFile, main);
diff --git a/webrtc/tools/rtcbot/test.js b/webrtc/tools/rtcbot/test.js
index e117398..abf0e17 100644
--- a/webrtc/tools/rtcbot/test.js
+++ b/webrtc/tools/rtcbot/test.js
@@ -6,14 +6,9 @@
 // in the file PATENTS.  All contributing project authors may
 // be found in the AUTHORS file in the root of the source tree.
 //
-// This script loads the test file in the virtual machine and runs it in a
-// context that only exposes a test variable with methods for testing and to
-// spawn bots.
-//
-// Note: an important part of this script is to keep nodejs-isms away from test
-// code and isolate it from implementation details.
+// Provides a Test class that exposes api to the tests.
+// Read test.prototype to see what methods are exposed.
 var fs = require('fs');
-var vm = require('vm');
 var BotManager = require('./botmanager.js');
 
 function Test() {
@@ -134,13 +129,6 @@
         doneCallback);
     }
   },
-}
+};
 
-function runTest(testfile) {
-  console.log("Running test: " + testfile);
-  var script = vm.createScript(fs.readFileSync(testfile), testfile);
-  script.runInNewContext({ test: new Test(), setInterval: setInterval,
-      setTimeout: setTimeout });
-}
-
-runTest(process.argv[2]);
+module.exports = Test;
diff --git a/webrtc/tools/rtcbot/test/ping_pong.js b/webrtc/tools/rtcbot/test/ping_pong.js
index feee3bc..1d9886e 100644
--- a/webrtc/tools/rtcbot/test/ping_pong.js
+++ b/webrtc/tools/rtcbot/test/ping_pong.js
@@ -6,8 +6,9 @@
 // in the file PATENTS.  All contributing project authors may
 // be found in the AUTHORS file in the root of the source tree.
 //
-function testPingPong(bot) {
-  test.log('bot:alice > Sending Ping to bot');
+function testPingPong(test, bot) {
+  test.assert(typeof bot.ping === 'function', 'Bot does not exposes ping.');
+
   bot.ping(gotAnswer);
 
   function gotAnswer(answer) {
@@ -16,4 +17,4 @@
   }
 }
 
-test.spawnBot("alice", "chrome", testPingPong);
+registerBotTest('testPingPong/chrome', testPingPong, ['chrome']);
diff --git a/webrtc/tools/rtcbot/test/simple_offer_answer.js b/webrtc/tools/rtcbot/test/simple_offer_answer.js
index e052598..43553f0 100644
--- a/webrtc/tools/rtcbot/test/simple_offer_answer.js
+++ b/webrtc/tools/rtcbot/test/simple_offer_answer.js
@@ -10,17 +10,12 @@
 //
 // Note: This test does not performs ice candidate exchange and
 // does not verifies that media can flow between the peers.
-function testOfferAnswer(peer1, peer2) {
-  test.wait([
-      createPeerConnection.bind(peer1),
-      createPeerConnection.bind(peer2) ],
-    establishCall);
+function testOfferAnswer(test, bot1, bot2) {
+  test.wait( [ bot1.createPeerConnection.bind(bot1, null),
+               bot2.createPeerConnection.bind(bot2, null) ],
+            run);
 
-  function createPeerConnection(done) {
-    this.createPeerConnection(null, done, test.fail);
-  }
-
-  function establishCall(pc1, pc2) {
+  function run(pc1, pc2) {
     test.log("Establishing call.");
     pc1.createOffer(gotOffer);
 
@@ -38,17 +33,16 @@
       pc2.setLocalDescription(answer, expectedCall, test.fail);
       pc1.setRemoteDescription(answer, expectedCall, test.fail);
     }
+
+    // TODO(andresp): Implement utilities in test to write expectations
+    // that certain methods must be called.
+    var expectedCalls = 0;
+    function expectedCall() {
+      if (++expectedCalls == 6)
+        test.done();
+    }
   }
 }
 
-// TODO(andresp): Implement utilities in test to write expectations that certain
-// methods must be called.
-var expectedCalls = 0;
-function expectedCall() {
-  if (++expectedCalls == 6)
-    test.done();
-}
-
-test.wait( [ test.spawnBot.bind(test, "alice", "chrome"),
-             test.spawnBot.bind(test, "bob", "chrome") ],
-          testOfferAnswer);
+registerBotTest('testOfferAnswer/chrome=>chrome',
+                testOfferAnswer, ['chrome', 'chrome']);
diff --git a/webrtc/tools/rtcbot/test/webrtc_video_streaming.js b/webrtc/tools/rtcbot/test/webrtc_video_streaming.js
index 62b7056..9142c53 100644
--- a/webrtc/tools/rtcbot/test/webrtc_video_streaming.js
+++ b/webrtc/tools/rtcbot/test/webrtc_video_streaming.js
@@ -11,11 +11,7 @@
 // and then write these stats to a file.
 //
 // Note: the source of the video and audio stream is getUserMedia().
-//
-function testVideoStreaming(bot1, bot2) {
-  var pc1 = null;
-  var pc2 = null;
-
+function testOneWayVideo(test, bot1, bot2) {
   var report = test.createStatisticsReport("webrtc_video_streaming");
 
   test.wait([
@@ -29,10 +25,8 @@
     }.bind(this), test.fail);
   }
 
-  function onPeerConnectionCreated(peer1, peer2) {
+  function onPeerConnectionCreated(pc1, pc2) {
     test.log("RTC Peers created.");
-    pc1 = peer1;
-    pc2 = peer2;
     pc1.addEventListener('addstream', test.fail);
     pc2.addEventListener('addstream', onAddStream);
     pc1.addEventListener('icecandidate', onIceCandidate.bind(pc2));
@@ -45,7 +39,7 @@
       pc1.addStream(stream);
       bot1.showStream(stream.id, true, true);
 
-      createOfferAndAnswer();
+      createOfferAndAnswer(pc1, pc2);
     }
   }
 
@@ -55,18 +49,18 @@
   }
 
   function onIceCandidate(event) {
-    if(event.candidate){
+    if(event.candidate) {
       test.log(event.candidate.candidate);
       this.addIceCandidate(event.candidate,
          onAddIceCandidateSuccess, test.fail);
-    };
+    }
 
     function onAddIceCandidateSuccess() {
       test.log("Candidate added successfully");
-    };
+    }
   }
 
-  function createOfferAndAnswer() {
+  function createOfferAndAnswer(pc1, pc2) {
     test.log("Creating offer.");
     pc1.createOffer(gotOffer, test.fail);
 
@@ -103,6 +97,5 @@
   }
 }
 
-test.wait( [ test.spawnBot.bind(test, "alice", "chrome"),
-             test.spawnBot.bind(test, "bob", "android-chrome") ],
-          testVideoStreaming);
+registerBotTest('testOneWayVideo/chrome=>chrome',
+                testOneWayVideo, ['chrome', 'chrome']);