Bug 828204 - Make if DEBUG statements internal to LOG in osfile. Add new TEST flag for logging into Console.services. r=yoric
authorYura Zenevich <yura.zenevich@gmail.com>
Sat, 16 Feb 2013 10:40:15 -0500
changeset 122132 36a17b72a5860c90e166b14d9f871b3abddf6e6f
parent 122131 3bb7a1eaa0218d0e20342ae4e630f42c38d1d68d
child 122133 9b4182f33b7f93d2089280a3e523c3f48a039e66
push id23118
push userryanvm@gmail.com
push dateSat, 16 Feb 2013 15:40:10 +0000
treeherdermozilla-inbound@9b4182f33b7f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyoric
bugs828204
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 828204 - Make if DEBUG statements internal to LOG in osfile. Add new TEST flag for logging into Console.services. r=yoric
toolkit/components/osfile/_PromiseWorker.jsm
toolkit/components/osfile/osfile_async_front.jsm
toolkit/components/osfile/osfile_async_worker.js
toolkit/components/osfile/osfile_shared_allthreads.jsm
toolkit/components/osfile/osfile_unix_allthreads.jsm
toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
--- a/toolkit/components/osfile/_PromiseWorker.jsm
+++ b/toolkit/components/osfile/_PromiseWorker.jsm
@@ -34,26 +34,26 @@ Queue.prototype = {
 };
 
 /**
  * An object responsible for dispatching messages to
  * a chrome worker and routing the responses.
  *
  * @param {string} url The url containing the source code for this worker,
  * as in constructor ChromeWorker.
- * @param {Function=} log Optionally, a logging function.
+ * @param {Function} log A logging function.
  *
  * @constructor
  */
 function PromiseWorker(url, log) {
   if (typeof url != "string") {
     throw new TypeError("Expecting a string");
   }
-  if (log != null && typeof log != "function") {
-    throw new TypeError("Expecting either null or a function");
+  if (typeof log !== "function") {
+    throw new TypeError("log is expected to be a function");
   }
   this._log = log;
   this._url = url;
 
   /**
    * The queue of deferred, waiting for the completion of their
    * respective job by the worker.
    *
@@ -93,19 +93,17 @@ PromiseWorker.prototype = {
      * of |OS.File.Error|. These are treated by |worker.onmessage|.
      * However, for other errors, we rely on DOM's mechanism for
      * serializing errors, which transmits these errors through
      * |worker.onerror|.
      *
      * @param {Error} error Some JS error.
      */
     worker.onerror = function onerror(error) {
-      if (self._log) {
-        self._log("Received uncaught error from worker", JSON.stringify(error.message), error.message);
-      }
+      self._log("Received uncaught error from worker", error.message);
       error.preventDefault();
       let {deferred} = self._queue.pop();
       deferred.reject(error);
     };
 
     /**
      * Receive messages from the worker, propagate them to the listeners.
      *
@@ -115,19 +113,17 @@ PromiseWorker.prototype = {
      *    some_error is an instance of |PromiseWorker.WorkerError|
      *
      * Messages may also contain a field |id| to help
      * with debugging.
      *
      * @param {*} msg The message received from the worker.
      */
     worker.onmessage = function onmessage(msg) {
-      if (self._log) {
-        self._log("Received message from worker", JSON.stringify(msg.data));
-      }
+      self._log("Received message from worker", msg.data);
       let handler = self._queue.pop();
       let deferred = handler.deferred;
       let data = msg.data;
       if (data.id != handler.id) {
         throw new Error("Internal error: expecting msg " + handler.id + ", " +
                         " got " + data.id + ": " + JSON.stringify(msg.data));
       }
       if ("ok" in data) {
@@ -153,24 +149,20 @@ PromiseWorker.prototype = {
    * garbage-collected before the message treatment is complete.
    *
    * @return {promise}
    */
   post: function post(fun, array, closure) {
     let deferred = Promise.defer();
     let id = ++this._id;
     let message = {fun: fun, args: array, id: id};
-    if (this._log) {
-      this._log("Posting message", JSON.stringify(message));
-    }
+    this._log("Posting message", message);
     this._queue.push({deferred:deferred, closure: closure, id: id});
     this._worker.postMessage(message);
-    if (this._log) {
-      this._log("Message posted");
-    }
+    this._log("Message posted");
     return deferred.promise;
   }
 };
 
 /**
  * An error that has been serialized by the worker.
  *
  * @constructor
--- a/toolkit/components/osfile/osfile_async_front.jsm
+++ b/toolkit/components/osfile/osfile_async_front.jsm
@@ -84,18 +84,17 @@ let clone = function clone(object) {
 
 /**
  * A shared constant used to normalize a set of options to nothing.
  */
 const noOptions = {};
 
 
 let worker = new PromiseWorker(
-  "resource://gre/modules/osfile/osfile_async_worker.js",
-  DEBUG?LOG:null);
+  "resource://gre/modules/osfile/osfile_async_worker.js", LOG);
 let Scheduler = {
   post: function post(...args) {
     let promise = worker.post.apply(worker, args);
     return promise.then(
       null,
       function onError(error) {
         // Decode any serialized error
         if (error instanceof PromiseWorker.WorkerError) {
--- a/toolkit/components/osfile/osfile_async_worker.js
+++ b/toolkit/components/osfile/osfile_async_worker.js
@@ -11,83 +11,65 @@ if (this.Components) {
 
 (function(exports) {
   "use strict";
 
    try {
      importScripts("resource://gre/modules/osfile.jsm");
 
      let LOG = exports.OS.Shared.LOG.bind(exports.OS.Shared.LOG, "Agent");
-     // A simple flag used to control debugging messages.
-     let DEBUG = exports.OS.Shared.DEBUG;
 
      /**
       * Communications with the controller.
       *
       * Accepts messages:
       * {fun:function_name, args:array_of_arguments_or_null, id:id}
       *
       * Sends messages:
       * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id}
       */
      self.onmessage = function onmessage(msg) {
        let data = msg.data;
-       if (DEBUG) {
-         LOG("Received message", JSON.stringify(data));
-       }
+       LOG("Received message", data);
        let id = data.id;
        let result;
        let exn;
        try {
          let method = data.fun;
-         if (DEBUG) {
-           LOG("Calling method", method);
-         }
+         LOG("Calling method", method);
          result = Agent[method].apply(Agent, data.args);
-         if (DEBUG) {
-           LOG("Method", method, "succeeded");
-         }
+         LOG("Method", method, "succeeded");
        } catch (ex) {
          exn = ex;
-         if (DEBUG) {
-           LOG("Error while calling agent method", exn, exn.stack);
-         }
+         LOG("Error while calling agent method", exn, exn.stack);
        }
        // Now, post a reply, possibly as an uncaught error.
        // We post this message from outside the |try ... catch| block
        // to avoid capturing errors that take place during |postMessage| and
        // built-in serialization.
        if (!exn) {
-         if (DEBUG) {
-           LOG("Sending positive reply", JSON.stringify(result), "id is", id);
-         }
+         LOG("Sending positive reply", result, "id is", id);
          if (result instanceof Transfer) {
            // Take advantage of zero-copy transfers
            self.postMessage({ok: result.data, id: id}, result.transfers);
          } else {
            self.postMessage({ok: result, id:id});
          }
        } else if (exn == StopIteration) {
          // StopIteration cannot be serialized automatically
-         if (DEBUG) {
-           LOG("Sending back StopIteration");
-         }
+         LOG("Sending back StopIteration");
          self.postMessage({StopIteration: true, id: id});
        } else if (exn instanceof exports.OS.File.Error) {
-         if (DEBUG) {
-           LOG("Sending back OS.File error", exn, "id is", id);
-         }
+         LOG("Sending back OS.File error", exn, "id is", id);
          // Instances of OS.File.Error know how to serialize themselves
          // (deserialization ensures that we end up with OS-specific
          // instances of |OS.File.Error|)
          self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id});
        } else {
-         if (DEBUG) {
-           LOG("Sending back regular error", exn, exn.stack, "id is", id);
-         }
+         LOG("Sending back regular error", exn, exn.stack, "id is", id);
          // Other exceptions do not, and should be propagated through DOM's
          // built-in mechanism for uncaught errors, although this mechanism
          // may lose interesting information.
          throw exn;
        }
      };
 
      /**
@@ -197,24 +179,24 @@ if (this.Components) {
      /**
       * The agent.
       *
       * It is in charge of performing method-specific deserialization
       * of messages, calling the function/method of OS.File and serializing
       * back the results.
       */
      let Agent = {
-       // Update DEBUG flag message from controller.
+       // Update worker's OS.Shared.DEBUG flag message from controller.
        SET_DEBUG: function SET_DEBUG (aDEBUG) {
-         DEBUG = aDEBUG;
+         exports.OS.Shared.DEBUG = aDEBUG;
        },
-       // Return current DEBUG value to controller.
+       // Return worker's current OS.Shared.DEBUG value to controller.
        // Note: This is used for testing purposes.
        GET_DEBUG: function GET_DEBUG () {
-         return DEBUG;
+         return exports.OS.Shared.DEBUG;
        },
        // Functions of OS.File
        stat: function stat(path) {
          return exports.OS.File.Info.toMsg(
            exports.OS.File.stat(Type.path.fromMsg(path)));
        },
        getCurrentDirectory: function getCurrentDirectory() {
          return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
--- a/toolkit/components/osfile/osfile_shared_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_shared_allthreads.jsm
@@ -96,17 +96,58 @@
        LOG = function() {
          let text = "OS";
          for (let i = 0; i < arguments.length; ++i) {
            text += (" " + arguments[i]);
          }
          dump(text + "\n");
        };
      }
-     exports.OS.Shared.LOG = LOG;
+
+     /**
+      * Apply JSON.stringify to an argument of type object.
+      * Return itself otherwise.
+      *
+      * @param arg An argument to be stringified if possible.
+      */
+     let stringifyArg = function stringifyArg(arg) {
+       if (typeof arg === "string") {
+         return arg;
+       }
+       if (arg && typeof arg === "object") {
+         return JSON.stringify(arg);
+       }
+       return arg;
+     };
+
+     /**
+      * A Shared LOG utility function that only logs when DEBUG is set.
+      *
+      * @params {string|object}
+      *         An arbitrary number of arguments to be logged.
+      */
+     exports.OS.Shared.LOG = function (...args) {
+       // If DEBUG is falsy, do nothing.
+       if (!exports.OS.Shared.DEBUG) {
+         return;
+       }
+
+       let logFunc = LOG;
+
+       if (exports.OS.Shared.TEST && Services) {
+         // If TEST is true and the file is loaded in the main thread,
+         // use Services.console for logging and listening to.
+         logFunc = function logFunc(...args) {
+           let message = ["TEST", "OS"].concat(args).join(" ");
+           Services.console.logStringMessage(message + "\n");
+         };
+       }
+
+       logFunc.apply(null, [stringifyArg(arg) for (arg of args)]);
+     };
 
      /**
       * An OS error.
       *
       * This class is provided mostly for type-matching. If you need more
       * details about an error, you should use the platform-specific error
       * codes provided by subclasses of |OS.Shared.Error|.
       *
@@ -378,51 +419,43 @@
        }
        if (!("value" in x)) { // Sanity check
          throw new TypeError("Number " + x.toSource() + " has no field |value|");
        }
        return x.value;
      };
 
      function projector(type, signed) {
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Determining best projection for", type,
-             "(size: ", type.size, ")", signed?"signed":"unsigned");
-       }
+       exports.OS.Shared.LOG("Determining best projection for", type,
+         "(size: ", type.size, ")", signed?"signed":"unsigned");
        if (type instanceof Type) {
          type = type.implementation;
        }
        if (!type.size) {
          throw new TypeError("Argument is not a proper C type");
        }
        // Determine if type is projected to Int64/Uint64
        if (type.size == 8           // Usual case
            // The following cases have special treatment in js-ctypes
            // Regardless of their size, the value getter returns
            // a Int64/Uint64
            || type == ctypes.size_t // Special cases
            || type == ctypes.ssize_t
            || type == ctypes.intptr_t
            || type == ctypes.uintptr_t
-           || type == ctypes.off_t){
-          if (signed) {
-	    if (exports.OS.Shared.DEBUG) {
-             LOG("Projected as a large signed integer");
-	    }
-            return projectLargeInt;
-          } else {
-	    if (exports.OS.Shared.DEBUG) {
-             LOG("Projected as a large unsigned integer");
-	    }
-            return projectLargeUInt;
-          }
+           || type == ctypes.off_t) {
+         if (signed) {
+           exports.OS.Shared.LOG("Projected as a large signed integer");
+           return projectLargeInt;
+         } else {
+           exports.OS.Shared.LOG("Projected as a large unsigned integer");
+           return projectLargeUInt;
+         }
        }
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Projected as a regular number");
-       }
+       exports.OS.Shared.LOG("Projected as a regular number");
        return projectValue;
      };
      exports.OS.Shared.projectValue = projectValue;
 
 
 
      /**
       * Get the appropriate type for an unsigned int of the given size.
@@ -815,19 +848,17 @@
       * @return null if the function could not be defined (generally because
       * it does not exist), or a JavaScript wrapper performing the call to C
       * and any type conversion required.
       */// Note: Future versions will use a different implementation of this
         // function on the main thread, osfile worker thread and regular worker
         // thread
      let declareFFI = function declareFFI(lib, symbol, abi,
                                           returnType /*, argTypes ...*/) {
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Attempting to declare FFI ", symbol);
-       }
+       exports.OS.Shared.LOG("Attempting to declare FFI ", symbol);
        // We guard agressively, to avoid any late surprise
        if (typeof symbol != "string") {
          throw new TypeError("declareFFI expects as first argument a string");
        }
        abi = abi || ctypes.default_abi;
        if (Object.prototype.toString.call(abi) != "[object CABI]") {
          // Note: This is the only known manner of checking whether an object
          // is an abi.
@@ -855,26 +886,22 @@
          let fun = lib.declare.apply(lib, signature);
          let result = function ffi(/*arguments*/) {
            let result = fun.apply(fun, arguments);
            return returnType.importFromC(result, symbol);
          };
          if (exports.OS.Shared.DEBUG) {
            result.fun = fun; // Also return the raw FFI function.
          }
-	 if (exports.OS.Shared.DEBUG) {
-          LOG("Function", symbol, "declared");
-	 }
+         exports.OS.Shared.LOG("Function", symbol, "declared");
          return result;
        } catch (x) {
          // Note: Not being able to declare a function is normal.
          // Some functions are OS (or OS version)-specific.
-	 if (exports.OS.Shared.DEBUG) {
-          LOG("Could not declare function " + symbol, x);
-	 }
+         exports.OS.Shared.LOG("Could not declare function ", symbol, x);
          return null;
        }
      };
      exports.OS.Shared.declareFFI = declareFFI;
 
      // A bogus array type used to perform pointer arithmetics
      let gOffsetByType;
 
--- a/toolkit/components/osfile/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_unix_allthreads.jsm
@@ -42,19 +42,17 @@ if (typeof Components != "undefined") {
   let libc_candidates =  [ "libSystem.B.dylib",
                            "libc.so.6",
                            "libc.so" ];
   for (let i = 0; i < libc_candidates.length; ++i) {
     try {
       libc = ctypes.open(libc_candidates[i]);
       break;
     } catch (x) {
-      if (exports.OS.Shared.DEBUG) {
-        LOG("Could not open libc "+libc_candidates[i]);
-      }
+      LOG("Could not open libc ", libc_candidates[i]);
     }
   }
   if (!libc) {
     throw new Error("Could not open any libc.");
   }
   exports.OS.Shared.Unix.libc = libc;
 
   // Define declareFFI
--- a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
+++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
@@ -135,16 +135,17 @@ let test = maketest("Main", function mai
     yield test_info_features_detect();
     yield test_read_write();
     yield test_read_write_all();
     yield test_position();
     yield test_copy();
     yield test_mkdir();
     yield test_iter();
     yield test_exists();
+    yield test_debug_test();
     info("Test is over");
     SimpleTest.finish();
   });
 });
 
 /**
  * A file that we know exists and that can be used for reading.
  */
@@ -648,9 +649,46 @@ let test_debug = maketest("debug", funct
     }
     testSetDebugPref(true);
     let workerDEBUG = yield OS.File.GET_DEBUG();
     test.is(workerDEBUG, true, "Worker's DEBUG is set.");
     testSetDebugPref(false);
     workerDEBUG = yield OS.File.GET_DEBUG();
     test.is(workerDEBUG, false, "Worker's DEBUG is unset.");
   });
+});
+
+/**
+ * Test logging in the main thread with set OS.Shared.DEBUG and
+ * OS.Shared.TEST flags.
+ */
+let test_debug_test = maketest("debug_test", function debug_test(test) {
+  return Task.spawn(function () {
+    // Create a console listener.
+    let consoleListener = {
+      observe: function (aMessage) {
+        // Ignore unexpected messages.
+        if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) {
+          return;
+        }
+        if (aMessage.message.indexOf("TEST OS") < 0) {
+          return;
+        }
+        test.ok(true, "DEBUG TEST messages are logged correctly.")
+      }
+    };
+    // Set/Unset OS.Shared.DEBUG, OS.Shared.TEST and the console listener.
+    function toggleDebugTest (pref) {
+      OS.Shared.DEBUG = pref;
+      OS.Shared.TEST = pref;
+      Services.console[pref ? "registerListener" : "unregisterListener"](
+        consoleListener);
+    }
+    // Save original DEBUG value.
+    let originalPref = OS.Shared.DEBUG;
+    toggleDebugTest(true);
+    // Execution of OS.File.exist method will trigger OS.File.LOG several times.
+    let fileExists = yield OS.File.exists(EXISTING_FILE);
+    toggleDebugTest(false);
+    // Restore DEBUG to its original.
+    OS.Shared.DEBUG = originalPref;
+  });
 });
\ No newline at end of file