Develop:Tests

From FreeNATS Wiki
Jump to: navigation, search

Developing Tests

Developing FreeNATS server-side PHP tests is quite easy. They consist of a PHP file defining a class containing one or more member functions.

You may also like to see the developing with FreeNATS documentation for some globally available features and information.

Example Test

To demonstrate how this is done we shall work through an example. We want to create a test that opens a web page and checks if it contains the text "online". The page we want to check is http://someserver/.

First we create a PHP file in the server/base/site/tests directory called something meaningful like pagetest.php (it must have a PHP extension and you must also have set the system variable site.include.tests to 1 which will cause FreeNATS to include all .php files in the server/site/tests directory on startup).

To keep the example as clean as possible the actual test is done by an external function check_page_text($url,$text) which returns true if the text is found and false if it isn't.

Example 1: Simple Test

<?php
function check_page_text($url,$text)
{
 $fp=fopen($url,"r"); // open the URL
 if ($fp<=0) return false; // fail if can't open
 $body="";
 while (!feof($fp))
  $body.=fgets($fp,1024); // read into the body in 1k chunks
 fclose($fp); // finished with pointer
 if (strpos($body,$text)===false) return false; // not found
 return true; // found
}

// Ok that was the actual text-check function now the actual integration within FreeNATS
global $NATS; // make sure we have the NATS object
// n.b. You may like to encase this in a ifset($NATS) block so you can build/test
// without having to be within the NATS environment.

class Pagecheck_Test extends FreeNATS_Local_Test // create a new class extending FreeNATS_Local_Test
{

 function DoTest($testname,$param,$hostname,$timeout,$params) // do the actual test (REQUIRED)
 {
 if (check_page_text("http://someserver/","online")==true) return 1;
 return 0;
 }

 function Evaluate($result) // be able to evaluate our result (REQUIRED)
 {
 if ($result>0) return 0; // FreeNATS passed (0) flag
 return 2; // FreeNATS failed (2) flag
 }
}

// Now we have defined the class we must register it with FreeNATS

$params=array(); // blank parameters array (see later for more detail)

$NATS->Tests->Register(
 "pagecheck",           // the internal simple test name (must not conflict with anything else)
 "Pagecheck_Test",      // the class name (above)
 $params,               // parameters (blank for now)
 "Page Online Checker", // the display name of the test in the interface
 1,                     // the revision number of the test
 "Our Page Checker");   // extended description for the test module used in overview

?>

And there you have it. In the simple example above the test will now appear to be added to a node. Recorded data (and last value) will be 0 for no matching text or 1 for a match and the evaluate function will show this as passed or failed.

Adding Parameters (Simply)

Parameters for your test can be simply offered to the user by defining them in the $params array. For each defined parameter the user interface will show the title given and a text input box. Whatever the user inputs and saves is then provided to the DoTest function in the param variable (the first parameter) and in params[0-9] ($params[0] is equal to $param).

To add the ability for the user to configure the remote URL and string to search for you can modify the code as follows:

Example 2. Adding Simple Parameters

class Pagecheck_Test extends FreeNATS_Local_Test // create a new class extending FreeNATS_Local_Test
{

 function DoTest($testname,$param,$hostname,$timeout,$params) // do the actual test (REQUIRED)
 {
 if (check_page_text($params[0],$params[1])==true) return 1; // use parameters 0 and 1
 return 0;
 }

 function Evaluate($result) // be able to evaluate our result (REQUIRED)
 {
 if ($result>0) return 0; // FreeNATS passed (0) flag
 return 2; // FreeNATS failed (2) flag
 }
}

// Now we have defined the class we must register it with FreeNATS

$params=array(
"URL/Full http qualified URL", // note the / use - text after it is displayed under the field in italics
"Search String");

$NATS->Tests->Register(
 "pagecheck",           // the internal simple test name (must not conflict with anything else)
 "Pagecheck_Test",      // the class name (above)
 $params,               // parameters (no longer blank)
 "Page Online Checker", // the display name of the test in the interface
 1,                     // the revision number of the test
 "Our Page Checker");   // extended description for the test module used in overview

In the code above we have specified two parameters. These are the titles that will be displayed in the test edit dialogue. The URL title has a /. If a title contains a / the text after it will be displayed in italics under the field name.

In this example the output would roughly resemble:

    URL: INPUT_BOX
    Full http qualified URL
    Search String: INPUT_BOX

Adding More Complex Parameters

If you need to have better control over the parameters you can implement a DisplayForm method in your class which returns a string containing the parameter options. This is output as part of the test edit dialogue which takes a reference to the test data array. This can then be used to create/populate your custom field types.

The variable array is the row record from the fnlocaltest table for this specific test and as such has available testparam and testparam1-9 as indexes.

Lets say we want to update our example to include URL-based authentication. We don't want the user to have to specify it manually in the URL. We could just add two new parameters to the array but we want to be clever and not display the password every time. We want to change it only if the user inputs something new (and of course have a checkbox to clear it as the user can't input nothing in this case).

Any change to the actual test function isn't shown here as that is not the point of this documentation!

Example 3. Adding More Complex Parameters

class Pagecheck_Test extends FreeNATS_Local_Test // create a new class extending FreeNATS_Local_Test
{

 function DoTest($testname,$param,$hostname,$timeout,$params) // do the actual test (REQUIRED)
 {
 if (check_page_text($params[0],$params[1],$params[2],$params[3])==true) return 1; // use parameters 0 and 1
 return 0;
 }

 function Evaluate($result) // be able to evaluate our result (REQUIRED)
 {
 if ($result>0) return 0; // FreeNATS passed (0) flag
 return 2; // FreeNATS failed (2) flag
 }

 function DisplayForm(&$test) // note the reference & is used
 {
 $out=""; // output buffer
 $out.="<table width=100% border=0>";
 $out.="<tr><td align=right>URL :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam value=\"".$test['testparam']."\">";
 $out.="<br><i>Fully-qualified URL including http://</i>";
 $out.="</td></tr>";
 $out.="<tr><td align=right>String :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam1 value=\"".$test['testparam1']."\">";
 $out.="<br><i>String to search for</i>";
 $out.="</td></tr>";

 // Now we have pretty-much replicated what was done with the old params array

 $out.="<tr><td align=right>Username :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam2 value=\"".$test['testparam2']."\">";
 $out.="<br><i>Specify to use HTTP-AUTH on the URL</i>";
 $out.="</td></tr>"; 

 // so far so much the same... but...
 $out.="<tr><td align=right>Password :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam3 value=\"\">"; // dont display it
 $out.="<input type=hidden name=keepparam3 value=1>"; // don't update testparam3 (if blank)
 $out.="<br><i>Enter a new password to set or... ";
 $out.="<input type=checkbox name=clearparam3 value=1> "; // clears testparam3 if set
 $out.="clear it</i>";
 $out.="</td></tr>";
 echo $out; // output the buffer
 }
}

// Now we have defined the class we must register it with FreeNATS

$params=array(); // a blank array now

$NATS->Tests->Register(
 "pagecheck",           // the internal simple test name (must not conflict with anything else)
 "Pagecheck_Test",      // the class name (above)
 $params,               // parameters (blank for now)
 "Page Online Checker", // the display name of the test in the interface
 1,                     // the revision number of the test
 "Our Page Checker");   // extended description for the test module used in overview

Securing API/View Output

Although all test accounts used in FreeNATS shouldn't be live or production-sensitive let's say we were really worried about the test data getting into the wrong hands when we request it as a JavaScript array for a view or data API call.

We can optionally implement a ProtectOutput function that takes a reference to the data array about to be output and modifies it as you wish. In this example we want to protect testparam3 which holds the HTTP-AUTH password if specified.

Example 4. Protecting API Output

class Pagecheck_Test extends FreeNATS_Local_Test // create a new class extending FreeNATS_Local_Test
{

 function DoTest($testname,$param,$hostname,$timeout,$params) // do the actual test (REQUIRED)
 {
 if (check_page_text($params[0],$params[1],$params[2],$params[3])==true) return 1; // use parameters 0 and 1
 return 0;
 }

 function Evaluate($result) // be able to evaluate our result (REQUIRED)
 {
 if ($result>0) return 0; // FreeNATS passed (0) flag
 return 2; // FreeNATS failed (2) flag
 }

 function ProtectOutput(&$test) // note the reference
 {
 $test['testparam3']=""; // blank it for output
 return true;
 }

Timing the Test

We can time the test through the use of a TFNTimer object. If possible tests should have their DNS pre-cached (or ideally pre-resolved and use the IP for testing) either by your own method or using the url_lookup() (for a FQDN URL) or ip_lookup() (for a hostname) globally available functions.

The TFNTimer object is simple enough as can be seen the example below. It will return a "safe" time i.e. always rounded to 4d.p. (ms accuracy) and a minumum of 0.0001 (which may not be strictly true if the time taken was 0.00001 or a "time shift" event such as NTP sync happened during the test execution).

We now change the test function to return a negative number on failure and the elapsed time on success. We don't need to update our Evaluate() function is it already returns passed for a result > 0 and failure for anything else.

Please note in the code below only the timer-specific new code is highlighted. The "new" URL HTTP-AUTH code is not as although not seen before it is not specific to this documentation.

Example 5. Timing the Test

<?php
function check_page_text($url,$text,$user="",$pass="")
{ 
 $timer=new TFNTimer(); // initialise the timer
 url_lookup($url); // pre-resolve the DNS into cache

 if ($user!="") // use HTTP-AUTH
  {
  $pos=strpos($url,"://");
  if ($pos===false) return -1; // not a valid URL
  $protocol=substr($url,0,$pos+3); // protocol section
  $uri=substr($url,$pos+3); // uri section
  $url=$protocol.$user.":".$pass."@".$uri; // make http://user:pass@uri
  }
 $timer->Start(); // start the timer
 $fp=fopen($url,"r"); // open the URL
 if ($fp<=0) return false; // fail if can't open
 // or we could start it here to not include opening times...
 $body="";
 while (!feof($fp))
  $body.=fgets($fp,1024); // read into the body in 1k chunks
 $elapsed=$timer->Stop(); // get the elapsed time at this point
 fclose($fp); // finished with pointer
 if (strpos($body,$text)===false) return -1; // not found
 return $elapsed; // return a positive elapsed time on success
}


Test Units

This is not shown in any of the actual code samples as it was implemented later

You can assign units in short and long form to your test after it has been registered with something like:

$NATS->Tests->Register("sometest","someclass",$params,"Some Tester",1,"Some Tester We Wrote");
$NATS->Tests->SetUnits("sometest","Seconds","s"); // long format first


Finished Total Example

Example 6: Finished Example

<?php
function check_page_text($url,$text,$user="",$pass="") // our actual test function
{ 
 $timer=new TFNTimer(); // initialise the timer
 url_lookup($url); // pre-resolve the DNS into cache

 if ($user!="") // use HTTP-AUTH
  {
  $pos=strpos($url,"://");
  if ($pos===false) return -1; // not a valid URL
  $protocol=substr($url,0,$pos+3); // protocol section
  $uri=substr($url,$pos+3); // uri section
  $url=$protocol.$user.":".$pass."@".$uri; // make http://user:pass@uri
  }
 $timer->Start(); // start the timer
 $fp=fopen($url,"r"); // open the URL
 if ($fp<=0) return false; // fail if can't open
 // or we could start it here to not include opening times...
 $body="";
 while (!feof($fp))
  $body.=fgets($fp,1024); // read into the body in 1k chunks
 $elapsed=$timer->Stop(); // get the elapsed time at this point
 fclose($fp); // finished with pointer
 if (strpos($body,$text)===false) return -1; // not found
 return $elapsed; // return a positive elapsed time on success
}

// Ok that was the actual text-check function now the actual integration within FreeNATS
global $NATS;
// n.b. You may like to encase this in a ifset($NATS) block so you can build/test
// without having to be within the NATS environment.

class Pagecheck_Test extends FreeNATS_Local_Test // create a new class extending FreeNATS_Local_Test
{

 function DoTest($testname,$param,$hostname,$timeout,$params) // do the actual test (REQUIRED)
 {
 if (check_page_text($params[0],$params[1],$params[2],$params[3])==true) return 1;
 return 0;
 }

 function Evaluate($result) // be able to evaluate our result (REQUIRED)
 {
 if ($result>0) return 0; // FreeNATS passed (0) flag
 return 2; // FreeNATS failed (2) flag
 }

 function ProtectOutput(&$test) // output protections (optional)
 {
 $test['testparam3']=""; // blank it for output
 return true;
 }

 function DisplayForm(&$test) // nice user form (optional)
 {
 $out=""; // output buffer
 $out.="<table width=100% border=0>";
 $out.="<tr><td align=right>URL :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam value=\"".$test['testparam']."\">";
 $out.="<br><i>Fully-qualified URL including http://</i>";
 $out.="</td></tr>";
 $out.="<tr><td align=right>String :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam1 value=\"".$test['testparam1']."\">";
 $out.="<br><i>String to search for</i>";
 $out.="</td></tr>";

 // Now we have pretty-much replicated what was done with the old params array

 $out.="<tr><td align=right>Username :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam2 value=\"".$test['testparam2']."\">";
 $out.="<br><i>Specify to use HTTP-AUTH on the URL</i>";
 $out.="</td></tr>"; 

 // so far so much the same... but...
 $out.="<tr><td align=right>Password :</td>";
 $out.="<td align=left>";
 $out.="<input type=text size=30 name=testparam3 value=\"\">"; // dont display it
 $out.="<input type=hidden name=keepparam3 value=1>"; // don't update testparam3 (if blank)
 $out.="<br><i>Enter a new password to set or... ";
 $out.="<input type=checkbox name=clearparam3 value=1> "; // clears testparam3 if set
 $out.="clear it</i>";
 $out.="</td></tr>";
 echo $out; // output the buffer
 }
}

// Now we have defined the class we must register it with FreeNATS

$params=array(); // blank parameters array as we have implemented DisplayForm above

$NATS->Tests->Register(
 "pagecheck",           // the internal simple test name (must not conflict with anything else)
 "Pagecheck_Test",      // the class name (above)
 $params,               // parameters (blank for now)
 "Page Online Checker", // the display name of the test in the interface
 1,                     // the revision number of the test
 "Our Page Checker");   // extended description for the test module used in overview

?>