Change @purpose to @brief (Doxygen warning) and work on unit tests
authorJeremy Tan <[email protected]>
Thu, 5 Sep 2013 13:18:32 +0000 (21:18 +0800)
committerJeremy Tan <[email protected]>
Thu, 5 Sep 2013 13:18:32 +0000 (21:18 +0800)
16 files changed:
server/common.h
server/control.c
server/control.h
server/fastcgi.c
server/fastcgi.h
server/log.c
server/log.h
server/main.c
server/options.h
server/sensor.c
server/sensor.h
server/thread.c
server/thread.h
testing/qunit/base64.js [new file with mode: 0644]
testing/qunit/index.html
testing/qunit/unit-tests.js

index eba4902..67ea195 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file common.h
- * @purpose Common header includes
+ * @brief Common header includes
  */
 
 #ifndef _COMMON_H
index 12e340a..59b5bf8 100644 (file)
@@ -1,3 +1,7 @@
+/**
+ * @file control.c
+ * @brief Handles all client control requests (admin/actuator related)
+ */
 #include "common.h"
 #include "control.h"
 
index cc8f327..2878a88 100644 (file)
@@ -1,6 +1,11 @@
+/**
+ * @file control.h
+ * @brief Header file for control functions
+ */
 #ifndef _CONTROL_H
 #define _CONTROL_H
 
+/**ID codes for all the actuators**/
 typedef enum Actuators {ACT_NONE = -1, ACT_PREG = 0, ACT_SOLENOID1} Actuators;
 extern void Control_Handler(FCGIContext *context, char *params);
 
index 2da4b12..cb12e85 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file fastcgi.c
- * @purpose Runs the FCGI request loop to handle web interface requests.
+ * @brief Runs the FCGI request loop to handle web interface requests.
  *
  * fcgi_stdio.h must be included before all else so the stdio function
  * redirection works ok.
 #include "control.h"
 #include "options.h"
 
+/**The time period (in seconds) before the control key expires @ */
 #define CONTROL_TIMEOUT 180
 
+/**Contextual information related to FCGI requests*/
 struct FCGIContext {
        /**The time of last valid user access possessing the control key*/
        time_t control_timestamp;
@@ -177,7 +179,7 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
  * Adds a key/value pair to a JSON response. The response must have already
  * been initiated by FCGI_BeginJSON. Note that characters are not escaped.
  * @param key The key of the JSON entry
- * &param value The value associated with the key.
+ * @param value The value associated with the key.
  */
 void FCGI_JSONPair(const char *key, const char *value)
 {
@@ -224,19 +226,6 @@ void FCGI_JSONKey(const char *key)
        printf(",\r\n\t\"%s\" : ", key);
 }
 
-/**
- * Should be used to write out the value of a JSON key. This has
- * the same format as the printf functions. Care should be taken to format
- * the output in valid JSON. 
- */
-void FCGI_JSONValue(const char *format, ...)
-{
-       va_list list;
-       va_start(list, format);
-       vprintf(format, list);
-       va_end(list);
-}
-
 /**
  * Ends a JSON response that was initiated by FCGI_BeginJSON.
  */
@@ -261,7 +250,6 @@ void FCGI_RejectJSON(FCGIContext *context)
  * @param context The context to work in
  * @param status The status the return data should have.
  * @param description A short description of why the input was rejected.
- * @param params The parameters that the module handler received.
  */
 void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description)
 {
@@ -278,6 +266,21 @@ void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *des
        FCGI_EndJSON();
 }
 
+/**
+ * Generates a response to the client as described by the format parameter and
+ * extra arguments (exactly like printf). To be used when none of the other
+ * predefined functions will work exactly as needed. Extra care should be taken
+ * to ensure the correctness of the output.
+ * @param format The format string
+ * @param ... Any extra arguments as required by the format string.
+ */
+void FCGI_PrintRaw(const char *format, ...)
+{
+       va_list list;
+       va_start(list, format);
+       vprintf(format, list);
+       va_end(list);
+}
 
 /**
  * Main FCGI request loop that receives/responds to client requests.
@@ -340,6 +343,4 @@ void * FCGI_RequestLoop (void *data)
        Thread_QuitProgram(false);
        // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return.
        return NULL;
-
-       
 }
index c4d2ef5..2003fd0 100644 (file)
@@ -1,16 +1,22 @@
 /**
  * @file fastcgi.h
- * @purpose Headers for the fastcgi web interface
+ * @brief Headers for the fastcgi web interface
  */
  
 #ifndef _FASTCGI_H
 #define _FASTCGI_H
  
-/**(HTTP) Status codes that fcgi module handlers can return**/
+/**
+ * Status codes that fcgi module handlers can return
+ * Success status codes have values > 0
+ * Failure status codes have values <(=) 0 
+ * Note: 0 is counted as an error code to minimise confusion
+ * with in-browser JSON parsing error codes
+ */
 typedef enum StatusCodes {
-       STATUS_OK = 200,
-       STATUS_ERROR = 400,
-       STATUS_UNAUTHORIZED = 401
+       STATUS_OK = 1,
+       STATUS_ERROR = -1,
+       STATUS_UNAUTHORIZED = -2
 } StatusCodes;
 
 typedef struct FCGIContext FCGIContext;
@@ -26,12 +32,20 @@ extern void FCGI_JSONLong(const char *key, long value);
 extern void FCGI_JSONDouble(const char *key, double value);
 extern void FCGI_JSONBool(const char *key, bool value);
 extern void FCGI_JSONKey(const char *key);
-extern void FCGI_JSONValue(const char *format, ...);
+extern void FCGI_PrintRaw(const char *format, ...);
 extern void FCGI_EndJSON();
 extern void FCGI_RejectJSON(FCGIContext *context);
 extern void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description);
 extern void * FCGI_RequestLoop (void *data);
-#define FCGI_PrintRaw FCGI_JSONValue // Functionality is identical
+
+/**
+ * Custom formatting function for the JSON value. To be used in 
+ * conjunction with FCGI_JSONKey. Care should be taken to ensure
+ * that valid JSON is produced.
+ * 
+ * @see FCGI_PrintRaw for calling syntax
+ */
+#define FCGI_JSONValue FCGI_PrintRaw
 
 #endif
 
index 7b67043..d964e61 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file log.c
- * @purpose Implement logging and error handling functions
+ * @brief Implement logging and error handling functions
  */
 
 
index 2969540..eec5e53 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file log.h
- * @purpose Declaration of functions for printing log messages and/or terminating program after a fatal error
+ * @brief Declaration of functions for printing log messages and/or terminating program after a fatal error
  */
 
 #ifndef _LOG_H
index ec21a25..b202418 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file main.c
- * @purpose main and its helper functions, signal handling and cleanup functions
+ * @brief main and its helper functions, signal handling and cleanup functions
  */
 
 // --- Custom headers --- //
index a15e14a..a20e0e1 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file options.h
- * @purpose Define the Options structure and the g_options variable
+ * @brief Define the Options structure and the g_options variable
  */
 
 #ifndef _OPTIONS_H
index 2e0749e..f58daa5 100644 (file)
@@ -1,10 +1,9 @@
 /**
  * @file sensor.c
- * @purpose Implementation of sensor thread
+ * @brief Implementation of sensor thread
  * TODO: Finalise implementation
  */
 
-
 #include "common.h"
 #include "sensor.h"
 #include "options.h"
@@ -12,7 +11,7 @@
 
 /** Array of sensors, initialised by Sensor_Init **/
 static Sensor g_sensors[NUMSENSORS]; //global to this file
-static const char * g_sensor_names[] = {"analog_test0","analog_test1","digital_test0","digital_test1"};
+static const char * g_sensor_names[] = {"analog_test0", "analog_test1", "digital_test0", "digital_test1"};
 /**
  * Read a data value from a sensor; block until value is read
  * @param sensor_id - The ID of the sensor
index 8b20077..c6cfbe0 100644 (file)
@@ -1,7 +1,6 @@
-
 /**
  * @file sensor.h
- * @purpose Declarations for sensor thread related stuff
+ * @brief Declarations for sensor thread related stuff
  */
 
 
index 57473ea..f1d0bb3 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file thread.c
- * @purpose Implementation of thread control
+ * @brief Implementation of thread control
  */
 
 #include "thread.h"
index 14ae785..1c14c9a 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @file thread.h
- * @purpose Declarations for thread control related functions and variables
+ * @brief Declarations for thread control related functions and variables
  */
 
 #ifndef _THREAD_H
diff --git a/testing/qunit/base64.js b/testing/qunit/base64.js
new file mode 100644 (file)
index 0000000..5baba0d
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2010 Nick Galbreath
+ * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* base64 encode/decode compatible with window.btoa/atob
+ *
+ * window.atob/btoa is a Firefox extension to convert binary data (the "b")
+ * to base64 (ascii, the "a").
+ *
+ * It is also found in Safari and Chrome.  It is not available in IE.
+ *
+ * if (!window.btoa) window.btoa = base64.encode
+ * if (!window.atob) window.atob = base64.decode
+ *
+ * The original spec's for atob/btoa are a bit lacking
+ * https://developer.mozilla.org/en/DOM/window.atob
+ * https://developer.mozilla.org/en/DOM/window.btoa
+ *
+ * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
+ * If any character is not [0,255], then an DOMException(5) is thrown.
+ *
+ * window.atob and base64.decode take a base64-encoded string
+ * If the input length is not a multiple of 4, or contains invalid characters
+ *   then an DOMException(5) is thrown.
+ */
+var base64 = {};
+base64.PADCHAR = '=';
+base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+base64.makeDOMException = function() {
+    // sadly in FF,Safari,Chrome you can't make a DOMException
+    var e, tmp;
+
+    try {
+        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
+    } catch (tmp) {
+        // not available, just passback a duck-typed equiv
+        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
+        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
+        var ex = new Error("DOM Exception 5");
+
+        // ex.number and ex.description is IE-specific.
+        ex.code = ex.number = 5;
+        ex.name = ex.description = "INVALID_CHARACTER_ERR";
+
+        // Safari/Chrome output format
+        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
+        return ex;
+    }
+}
+
+base64.getbyte64 = function(s,i) {
+    // This is oddly fast, except on Chrome/V8.
+    //  Minimal or no improvement in performance by using a
+    //   object with properties mapping chars to value (eg. 'A': 0)
+    var idx = base64.ALPHA.indexOf(s.charAt(i));
+    if (idx === -1) {
+        throw base64.makeDOMException();
+    }
+    return idx;
+}
+
+base64.decode = function(s) {
+    // convert to string
+    s = '' + s;
+    var getbyte64 = base64.getbyte64;
+    var pads, i, b10;
+    var imax = s.length
+    if (imax === 0) {
+        return s;
+    }
+
+    if (imax % 4 !== 0) {
+        throw base64.makeDOMException();
+    }
+
+    pads = 0
+    if (s.charAt(imax - 1) === base64.PADCHAR) {
+        pads = 1;
+        if (s.charAt(imax - 2) === base64.PADCHAR) {
+            pads = 2;
+        }
+        // either way, we want to ignore this last block
+        imax -= 4;
+    }
+
+    var x = [];
+    for (i = 0; i < imax; i += 4) {
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
+            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
+        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
+    }
+
+    switch (pads) {
+    case 1:
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
+        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
+        break;
+    case 2:
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
+        x.push(String.fromCharCode(b10 >> 16));
+        break;
+    }
+    return x.join('');
+}
+
+base64.getbyte = function(s,i) {
+    var x = s.charCodeAt(i);
+    if (x > 255) {
+        throw base64.makeDOMException();
+    }
+    return x;
+}
+
+base64.encode = function(s) {
+    if (arguments.length !== 1) {
+        throw new SyntaxError("Not enough arguments");
+    }
+    var padchar = base64.PADCHAR;
+    var alpha   = base64.ALPHA;
+    var getbyte = base64.getbyte;
+
+    var i, b10;
+    var x = [];
+
+    // convert to string
+    s = '' + s;
+
+    var imax = s.length - s.length % 3;
+
+    if (s.length === 0) {
+        return s;
+    }
+    for (i = 0; i < imax; i += 3) {
+        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
+        x.push(alpha.charAt(b10 >> 18));
+        x.push(alpha.charAt((b10 >> 12) & 0x3F));
+        x.push(alpha.charAt((b10 >> 6) & 0x3f));
+        x.push(alpha.charAt(b10 & 0x3f));
+    }
+    switch (s.length - imax) {
+    case 1:
+        b10 = getbyte(s,i) << 16;
+        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+               padchar + padchar);
+        break;
+    case 2:
+        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
+        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
+        break;
+    }
+    return x.join('');
+}
index e666fce..faf4b51 100644 (file)
@@ -6,10 +6,36 @@
                <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.12.0.css">
         <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
         <script src="http://code.jquery.com/qunit/qunit-1.12.0.js"></script>
-    </head>
+        <!-- For IE support -->
+        <script src="base64.js"></script>
+    <style>
+      body {
+        font-family: Verdana, Arial, Sans;
+      }
+    </style>
+  </head>
 <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="unit-tests.js"></script>
+
+  <div style="margin:1em auto; width: 20em;">
+    <h3>Access control</h3>
+    <form id="control" action="#">
+      <table>
+        <tr>
+          <td>Username:</td>
+          <td><input type="text" id="username"></td>
+        </tr>
+        <tr>
+          <td>Password:</td>
+          <td><input type="password" id="password"></td>
+        </tr>
+        <tr>
+          <td><input type="submit" value="Submit"></td>
+        </tr>
+      </table>
+    </form>
+  </div>
 </body>
 </html>
\ No newline at end of file
index 2bab3f8..12bb76a 100644 (file)
@@ -2,12 +2,17 @@
  * MCTX3420 2013 - Remote pressurised can experiment.
  * Unit testing for the server API.
  * These unit tests use the QUnit unit testing framework.
- * @requires QUnit and jQuery
+ * @requires QUnit, jQuery, and base64.js
  * @date 28/8/13
  * @author Jeremy Tan
  */
 
-var api = location.protocol + "//" +  location.host + "/api/";
+//Namespace ut
+
+ut = {};
+ut.api = location.protocol + "//" +  location.host + "/api/";
+ut.ckey = undefined;
+ut.controlcb = $.Callbacks();
 
 /**
  * Sends an AJAX query to the API
@@ -15,54 +20,38 @@ var api = location.protocol + "//" +  location.host + "/api/";
  * @param {Object} opts Object holding the parameters, username, password and
  *                 callback. The parameters should be an object of key/value
  *                 pairs.
- * @returns JSON data
+ * @returns jqXHR object (but calls callback with JSON data, or null on AJAX error)
  */
 function query(module, opts) {
-  function buildQuery(opts) {
-    var result = "?";
-    var first = true;
-    
-    for (key in opts) {
-      if (!first) 
-        result += "&";
-      else 
-        first = false;
-      result += encodeURIComponent(key) + 
-                ((opts[key] !== undefined) ? "=" + encodeURIComponent(opts[key]) : "");
-    }
-    return result;
-  }
-  
-  var queryurl = api + module;
-  if (opts.params)
-    queryurl += buildQuery(opts.params);
+  var queryurl = ut.api + module;
   
   var authfunc;
   if (opts.username) {
     authfunc = function(xhr) {
       xhr.setRequestHeader("Authorization",
-        "Basic " + btoa(opts.username + ":" + opts.password));
+        "Basic " + base64.encode(opts.username + ":" + opts.password));
     };
   }
   
-  $.ajax({
+  return $.ajax({
     url: queryurl,
     type: 'GET',
     dataType: 'json',
-    beforeSend: authfunc
+    data: opts.params,
+    beforeSend: authfunc,
+    async: opts.async
   }).done(opts.callback)
     .fail(function(jqXHR) {
-      alert("Request Failed!");
       ok(false, "Request failed: " + jqXHR.status.toString() + " " + jqXHR.statusText);
       opts.callback(null);
     });
 }
 
 QUnit.module("API basics");
-QUnit.asyncTest("Existence (identify)", function () {
+QUnit.asyncTest("API Existence (identify)", function () {
   query("identify", {callback : function(data) {
    start();
-   ok(data.status >= 0, "Return status");
+   ok(data.status > 0, "Return status");
    ok(data.description, data.description);
    ok(data.build_date, data.build_date);
   }});
@@ -79,7 +68,7 @@ QUnit.module("Sensors");
 QUnit.asyncTest("Existence", function() {
   query("sensors", {params : {id : 0}, callback : function(data) {
    start();
-   ok(data.status >= 0, "Return status");
+   ok(data.status > 0, "Return status");
    ok(data.data !== undefined, "Data field existence");
    var result = "Data: ";
    for (var i = 0; i < data.data.length; i++) {
@@ -103,15 +92,50 @@ QUnit.asyncTest("Invalid sensor id 2", function() {
   }});  
 });
 
+QUnit.asyncTest("Out of bounds sensor id 1", function() {
+  query("sensors", {params : {id : "-1"}, callback : function(data) {
+   start();
+   ok(data.status < 0, "Return status");
+  }});  
+});
+
+QUnit.asyncTest("Out of bounds sensor id 2", function() {
+  query("sensors", {params : {id : "999"}, callback : function(data) {
+   start();
+   ok(data.status < 0, "Return status");
+  }});  
+});
+
 QUnit.module("Controls and access");
 QUnit.asyncTest("Gaining access", function() {
-  query("control", {params : {action : "start", force : true}, 
-                  username : "mctxadmin", password : "admin", 
-                  callback : function(data) {
-   start();
-   ok(data.status >= 0, "Return status");
-   
-   var key = data.key;
-   
-  }});
+  ut.controlcb.add(function () {
+    query("control", {params : {action : "start", force : true}, 
+                    username : $("#username").val(), password : $("#password").val(),
+                    async : false, 
+                    callback : function(data) {
+     start();
+     ok(data.status > 0, "Return status");
+     ut.ckey = data.key;
+    }});
+  });
+});
+
+QUnit.asyncTest("Setting actuator value", function () {
+  ut.controlcb.add(function () {
+    query("control", {params : {action : "set", id : 0,
+          username : $("#username").val(), password : $("#password").val(),
+          value : 200, key : ut.ckey},
+        callback : function(data) {
+          start();
+          ok(data.status > 0, "Return status");
+          ok(true, data.description);
+    }});
+  });
 });
+
+$(document).ready(function(){
+  $("#control").submit(function () {
+    ut.controlcb.fire();
+    return false;
+  });
+});
\ No newline at end of file

UCC git Repository :: git.ucc.asn.au