Tuesday, November 20, 2012

CF: ColdFusion with Amazon Load Balancer (ELB) and mysterious line breaks causing "unterminated string literal" exceptions

This seems like a complicated scenario at first until I started thinking about it again.
You use ColdFusion in the Cloud, specifically in the Amazon cloud, you then add a load balancer in front of your servers, then you notice your JavaScript starting to error out in some browsers. You cannot explain it.
Then, you spent countless hours going nuts.

Here is a sample JavaScript and ColdFusion block that would break. The simple block retrieved a name from a ColdFusion function and passed it to JavaScript:


<cfoutput>
  <script type="text/javascript">
    var myName = '#fCallForName()#';
  </script>
</cfoutput>


The above returned this in browser lets say the name was "John" :



  <script type="text/javascript">
    var myName = '
John';
  </script>


This, in turn, throws a "unterminated string literal" exception in JavaScript because of the line break right before the name. Of course, you say, silly you, you probably doing something in CF to return a Newline character combination. Good thought! So I changed the CF side to be as simple as possible. Here is the CF code that returns an empty string; guaranteed!



<cffunction name="fCallForName" returntype="string">
<cfreturn "">
</cffunction>




So nope, empty string still produced new line in the JavaScript output. So a few brain cycles go by and
I get to thinking to check to hit servers directly (bypassing amazon ELB altogether) and see what is returned. The return this time is enlightening, there is a blank string rather than the expected empty string, but no newline.



  <script type="text/javascript">
    var myName = ' ';
  </script>



Yeah, progress, maybe? This is different, which again it should not be. Thus, the only thing this establishes is that the Amazon load balancer (ELB) is changing the output stream somehow. Then, I also remembered an earlier blog post of mine where I outlined that CF inserts an empty space character when the function output is used directly in place in HTML context:  http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html
... and the fog began to lift.

I immediately reassigned the function output to a variable like so:

<cfset userName = fCallForName()>

Then, used the variable to output the content of the function:



<cfset userName = fCallForName()>
<cfoutput>
  <script type="text/javascript">
    var myName = '#userName#';
  </script>
</cfoutput>



...and bingo, everything started to work again. No more JavaScript errors. However the conclusions here are more scary then the error:

a) ColdFusion does introduce more than just a space character when function output is used in place
b) Amazon elastic load balancer makes modifications (corrections?) to the network stream and changes the output. For each in place output of function call it adds a line break.

Both a) and b) should not happen, but they do ;o(
At least now my pain could be your gain. Cloud pittfals, I guess...simple assumptions like surely the load balancer will not modify my data could throw you off .

Cheers,
B.





Thursday, October 18, 2012

CF: CFCamp 2012 more of everything

CFCamp 2012 is over. It was another whirlwind affair with more people, more speakers, more topics and more sponsors.
Overall a good gathering that had some nuggets to take away and think about.
Thanks everyone who stayed for my late presentation on practical application security.

You can download presentation slides if you want to review them at your leisure.

Cheers,
B.

Friday, October 12, 2012

CF: CFCamp here we go

Another year, another CFCamp.
The ColdFusion faithful trek into the southerly realms of Munich, Germany to learn all about the intricacies of living the code - dream ;o) So I am my way to join the fine folks, raise a Stein, and catch up on all that is newsworthy.
There will be a selection of topics on HTML5 and mobile since this is a pattern that has been steadily gaining ground among CF'ers and is reflected in this conference.
In addition there is a smaller CFAcademy segment with expanded hands on training.
If you are participating in Security section of CFAcademy watch this blog as I will post some course materials shortly.

CFAcademy users please download your exercise materials.
You will need :

  • One of the CFML engines (Adobe, Railo, OpenBD) installed. I will use Railo to work through examples.
  • A database: MySQL
  • Create a data source "book1" using the sql dump.


Cheers,
Bilal

Saturday, September 29, 2012

Sencha: NCDevCon Presentation Materials

Thanks everyone for attending my presentation on Sencha Architect. I hope the Movie Finder project will provide some insight into how to use Architect to build / prototype your own apps.
I am taking a moment to post the materials I used; hope this makes things clear as mud :o)

Presentation Slides: MVC App in Hours (pdf)

Step-by-Step: Building the Movie Finder App in Sencha Architect (pdf)

Cheers,
B.

Thursday, September 13, 2012

NCDevCon 2012: Regional Developer Conference with focus on Mobile, Web, and ColdFusion


Ok. Now it has been a cool four years that the Triangle Area ColdFusion User’s Group (TACFUG) is putting on a conference NCDevCon 2012 (September 29./30.) for regional developers (everyone is welcome). The user group members are putting in countless volunteer hours to create a conference for developers that everyone can benefit from.
Though, as every year, there is some effort to focus the presentations. This year the focus areas are Mobile, Web, and ColdFusion, though there is a good selection of general topics as well.

Thus, the conference continues to manage to have broad coverage of many relevant areas of ColdFusion, Web and Mobile development while also giving beginners options for hands on sessions.

All this is available for a small fee ($200) compared to $800 to $1000 of dollars we commonly pay. So this is definitely a deal in light of the knowledge that is being shared.

I have been selected to do a presentation on the Mobile side. This year’s topic is “Design MVC Mobile App Visually In Hours”. I am doing a walk-through of the Sencha architect tool as well as some other GUI development for mobile. Lots of demoing so nothing will ever go wrong ;o)

There is still time to book and space available so I hope to connect with everyone there.


Cheers,
-B

Wednesday, August 29, 2012

CF: MXunit Automatically Generating Tests and the Challenge of Test Scope

Let me start by saying that I like unit testing . It is very valuable tool in the arsenal of developers to fight the ever present monsters of recursion bugs and integration nightmares.
However, I will also freely admit that there is no agreement on how many unit tests are ever enough. Put a three developers in a room and you will get three different answers and maybe a headache to boot.

Also, in my case, the other challenge was to determine whether I had sufficient permutations of tests to verify that unit test goals could be met. Mind you that, test-coverage does not equal code coverage, but it probably is a good proxy. So, another goal of mine is to be able to think through all kinds of ways to call on code even the bad stuff and ensure that it behaves as expected. There is system in this, however, and this is what we use come up with most of the tests.

With MXUnit the world of ColdFusion has had a sturdy companion to write all these nice unit tests, however, we needed more. We wanted to have a good number of  tests especially for cfc (ColdFusion components). Coming up with the variety and permutation of tests does require quite a bit of hand coding, so we were looking for an easier way.

The solution we came up with was to generate test stubs. Many, many, many test stubs. We then review the cases and expand the ones we think cover the objective sufficiently. We end up deleting quite a few generated test, but this gives us a good baseline, especially for things we don't commonly unit test but we should, e.g. sending in a number when a string is expected, sending complex values, when simple ones will do and vice versa.

This may not be the way you want to work all test cases but helps to take care of many. We look at a functions parameters and generate all combinations of parameters with standard and break values. This can amount to be many thousandths so use with care.

The generator sample code I am attaching has been squeezed into one code file (cfm template) so it is easier to post. Place the code content into a file named "GenerateTestsPublic.cfm" under your mxunit path. Not optimal but the concepts should be visible. Take it for a spin and make adjustments. Feel free to add comments to the blog post.

Happy Experimenting:

GenerateTestsPublic.cfm:

<!--- 
 Generate mxUnit tests given a component name.
 One File per component function will be generated in this directory.
 
 Will attempt to generate all permutations possible.
 Random values
 Break Values
 
 Prefix:
 Test_[componentName]_[functionName].cfc
 
 An overall TestSuite will be generated
 
 TestSuite_[componentName].cfm
 
 Distributed under Apache 2 Lincese
 (c) 2012 Bilal Soylu 
  --->




<!DOCTYPE HTML>

<html>
<head>
 <title>Generate MXUnit Test for Components</title>
</head>

<body>

<CFIF IsDefined("Form.objectName") AND Trim(Form.objectName) NEQ "">
  
 <!--- get object info  --->
 <cftry>
  <cfset objReg = CreateObject("COMPONENT","#Trim(Form.objectName)#")>
  <cfset stcMeta = getMetaData(objReg)>
 
  
  <!--- generate directory  --->
  
  <!--- generate test files  --->
  <!--- save object name  --->
  <cfscript>
   //save main object
   strName = UCase(Trim(Form.objectName));
   strUseObjectNameInDir = ReplaceNoCase(strName,".","_","ALL");
   strDirPrefix = "unitTests";
   strHint="";
   arrTestCaseNames = [];
   //max test cases (high number of combinations can exist
   intMaxCases = -1;
   intMaxCasesPerFile = 1000;
   if (IsDefined("Form.maxTests") and Val(Form.maxTests) GT 0) intMaxCases = Val(Form.maxTests);
   if (IsDefined("Form.maxTestsPerFile") and Val(Form.maxTestsPerFile) LT intMaxCases) intMaxCasesPerFile = Val(Form.maxTestsPerFile);
   //names & paths
   strBasePath = GetDirectoryFromPath(GetCurrentTemplatePath()) & strDirPrefix & "\";
   strFilePrefix = "Test_#strUseObjectNameInDir#_"; 
   strTestSuiteName = "TestSuite_#strUseObjectNameInDir#.cfm";
   strTestSuitePath = strBasePath & strTestSuiteName;
   strTestCaseDirName = "TestCases_#strUseObjectNameInDir#";
   strTestCaseDirPath = strBasePath & "" & strTestCaseDirName;
   blnComplete=false;
   crlf = chr(13) & chr(10);
   tab = chr(9);
   
   //create directory
   if (NOT DirectoryExists(strTestCaseDirPath)) DirectoryCreate(strTestCaseDirPath);
   
   //sample data
   sampleStructure= ":" & SerializeJSON({"number"=9999,'text'='my text value','dte'=Now()});
   sampleArray= ":" & SerializeJSON(["a","b",33,{"number"=9999,'text'='my text value','dte'=Now()}]);
   sampleQuery = QueryNew("");
   FastFoodArray = ["French Fries","Hot Dogs","Fried Clams","Thick Shakes"];
   nColumnNumber = QueryAddColumn(sampleQuery, "FastFood", "VarChar", FastFoodArray);
   
  
   
  </cfscript>
  
  
  <!--- assemble files  --->
  <cfif IsDefined("stcMeta.Functions") AND ArrayLen(stcMeta.Functions) GT 0>
   <cfloop index="i" from="1" to="#ArrayLen(stcMeta.Functions)#" step="1">
    <!--- each functions has its on test case. init var containers  --->
    <cfset stcFunc = stcMeta.Functions[i]>
    <cfset selParaCombinations = QueryNew("")>
    <cfset stcQueries = {}>
    <cfparam name="stcFunc.Access" default="public">
    <cfif stcFunc.Access IS "public">
     
     <cfset strMethod = UCase(stcFunc.name)>
     <cfset strMethodHint = "">
     <cfset strTestCaseName = strFilePrefix & strMethod>
     <cfset strTestCaseFileName= strTestCaseDirPath & "\#strTestCaseName#.cfc">
     
     <!--- output (bsoylu 03-29-2012) --->
     <cfoutput>
     <br>processing: #strMethod#<br>
     </cfoutput>
     
     <!--- start testCase tC variable  --->
     <cfset genStartTc()>    
     
     <!--- init  --->
     <cfset arrParams = stcFunc.Parameters>     
     
     
     <!--- iterate through each paramter and set base test values  --->
     <cfif (ArrayLen(arrParams) GT 0)>
      <cfset strQList = "">
      <cfloop from="1" to="#ArrayLen(arrParams)#" index="y">
       <cfset stcPara = arrParams[y]>       
       <cfset strParaName= UCase(stcPara.Name)>
       <!--- create query and query handle  --->
       
       <cfset stcQueries["q_#strParaName#"] = QueryNew(strParaName,"CF_SQL_VARCHAR")>
       <cfset selQ = stcQueries["q_#strParaName#"]>
       <cfset strQList = ListAppend(strQList,"q_#strParaName#")>
       
       <!--- if parameter is not required it can be null as a valid state  --->      
       <cfif NOT (IsDefined("stcPara.Required") AND stcPara.Required)>
        <cfset QueryAddRow(selQ)>
        <cfset QuerySetCell(selQ,strParaName,"NULL")>        
       </cfif>
       <!--- by type  --->
       <!--- we prefix with equal sign if we need to eval the data later in processing --->
       <!--- we prefix with colon (:) when we need to deserialize JSON  --->
       <cfif IsDefined("stcPara.Type")>
        <cfif stcPara.Type IS "numeric">
         <!--- for each numeric: use max, use min, use zero, use random  --->
         <!--- Java Long: 9223372036854775807  and -9223372036854775808  --->
         
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"999999999")>         
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"-999999999")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"0")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"#RandRange(0,999999999)#")>         
        </cfif>
        
        <cfif stcPara.Type IS "string">
         <!--- for each string: use max, use empty, use "coldFusion"  --->
         <cfset strLongString = RepeatString("a",4000)>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"#strLongString#")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"coldFusion")>         
        </cfif>
       
        <cfif stcPara.Type IS "struct" OR stcPara.Type IS "any" >
         <!--- for each struct: use empty, use test struct  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,sampleStructure)>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=StructNew()")>         
        </cfif>
        
        <cfif stcPara.Type IS "date" >
         <!--- for each date: use "1/1/1980" use today use tomorrow, use 12/31/2200  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(1980,1,1)")>          
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=Now()")> 
         <cfset tomorrow = DateAdd("d",1,Now())>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(#Year(tomorrow)#,#Month(tomorrow)#,#Day(tomorrow)#)")> 
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(2200,12,31)")>                 
        </cfif>        
       
       
        <cfif stcPara.Type IS "array" >
         <!--- for each array: add sample array and empty  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=ArrayNew(1)")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,sampleArray)>                                 
        </cfif>  
        
        <cfif stcPara.Type IS "boolean" >
         <!--- for each bool: true/false  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"Yes")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"No")>                                 
        </cfif> 
        
        <cfif stcPara.Type IS "query" >
         <!--- for each query: sample and empty --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(sampleQuery)#")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(QueryNew(""))#")>                                 
        </cfif>                 
               
       <cfelse>
        <!--- valid state for non-typed or unknown paramters  --->
        <!--- for each undefined type (any): use structure  --->
        <cfset QueryAddRow(selQ)>
        <cfset QuerySetCell(selQ,strParaName,sampleStructure)>       
       
       </cfif>
      
       <!--- for the first parameter query we also set the query result. If there is only one parameter this is what will be returned --->
       <cfif y IS 1>
        <cfset selParaCombinations = selQ>
       </cfif>
      </cfloop><!--- loop through paramters  --->
     
     
      
      <!--- build cartesian product of queries (this will show us all the needed combinations  --->
      <cfset qCounter = 0>
      <cfloop list="#strQList#" index="qName">
       <cfset qCounter ++> 
       <cfif qCounter GT 1>
        <!--- combine into cartesian set. We can only do two queries at a time  --->
        <cfset qToAdd = stcQueries[qName]>
        <cfquery name="selParaCombinations" dbtype="query">
         SELECT DISTINCT *
         FROM selParaCombinations,qToAdd   
        </cfquery>       
       </cfif>  
          
      </cfloop>
      <!--- determine max loop (bsoylu 03-30-2012) --->
      <cfset intMaxLoop = selParaCombinations.RecordCount>
      <cfif intMaxCases GT 0>
       <cfset intMaxLoop = intMaxCases>
      </cfif>
      
      <cfoutput>
       found #selParaCombinations.RecordCount# test combinations 
       <cfif intMaxCases GT 0 AND intMaxCases LT selParaCombinations.RecordCount>
        <b>only #intMaxCases#</b> will be generated      
       </cfif>
       <br>
      </cfoutput>
      <cfflush>
      <!--- loop and generate test for file (bsoylu 03-29-2012) --->
      <cfset intTestCount = 0>
      <cfset intFileCount = 0>
      <cfloop query="selParaCombinations" endrow="#intMaxLoop#"> 
       <cfset tf="">
       <cfset intTestCount ++ >
       <!--- create para structure (bsoylu 03-29-2012) --->
       <cfset stcPara = {}>
       <cfloop list="#selParaCombinations.ColumnList#" index="idxCol">        
        <cfset strVal=Evaluate("selParaCombinations.#idxCol#")>
        <!--- check whether we need to further process the content (bsoylu 03-29-2012) --->
        <cfif strVal neq "NULL">
         <cftry>
          <cfif strVal is "">
           <cfset stcPara[idxCol] = strVal>
          <cfelseif Left(strVal,1) IS ":">
           <cfset stcPara[idxCol] = DeserializeJSON( Right(strVal,Len(strVal)-1))>
          <cfelseif Left(strVal,1) IS "=">
           <cfset stcPara[idxCol] = Evaluate( Right(strVal,Len(strVal)-1))>
          <cfelse>
           <cfset stcPara[idxCol] = strVal>
          </cfif> 
          
          <cfcatch>
           <hr>
           <cfoutput>could not interpret #Right(strVal,Len(strVal)-1)#</cfoutput>
           </hr>
           <cfabort>
          </cfcatch>
         </cftry> 
        </cfif>      
       </cfloop>
      
      
       
       <!--- create function for this para combination (bsoylu 03-29-2012) --->
       <cfset strJSON = SerializeJSON(stcPara)>
       <cfset tf=tf & crlf>     
       <cfset tf=tf & crlf & tab & '<cffunction name="test_#strMethod#_#NumberFormat(selParaCombinations.CurrentRow,"00000")#" >''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var strJSONPara ='''  & strJSON &'''>''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var response ="">''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var argCol = DeserializeJSON(strJSONPara)>''>
       <cfset tf=tf & crlf & tab & tab & '<cfinvoke argumentcollection="##argCol##" component="#strName#" method="#strMethod#" returnvariable="response"/>''>
       
       <cfset tf=tf & crlf & tab & tab & '<cfset assertNotEquals( "",response,SerializeJSON(response) & " - failed call with' & Replace(strJSON,'"','""','ALL') & '")>''>
       <cfset tf=tf & crlf & tab & '</cffunction>''> 
       <cfset tf=tf & crlf>     
      
       <!--- add to test file content (bsoylu 03-29-2012) --->
       <cfset tc=tc & crlf & tf>
       
       <cfif intTestCount mod intMaxCasesPerFile IS 0>
        <!--- write what we have so far (bsoylu 04-03-2012) --->
        <cfset intFileCount++>
        <cfset strModTestCaseName = strTestCaseName & "_File" & intFileCount>
        <cfset tC = tC & crlf & '</cfcomponent>''>
        <!--- set numbered File names (bsoylu 04-03-2012) --->
        <cfset strModTestCaseFileName= strTestCaseDirPath & "\#strModTestCaseName#.cfc">
        <cfset ArrayAppend(arrTestCaseNames,strModTestCaseName)>
        <cffile action="WRITE" file="#strModTestCaseFileName#" output="#tc#" addnewline="No">
        
        <cfoutput>generated sub case file: #strModTestCaseName#<br></cfoutput>
        <!--- reset tc for next file (bsoylu 04-03-2012) --->
        <cfset genStartTc("_File" & intFileCount + 1)>
       </cfif>
       
       
      </cfloop> <!--- set parameters (bsoylu 03-29-2012) --->
      
     </cfif> <!--- we have parameters  --->
     
     <!--- complete the last file (bsoylu 04-03-2012) --->
     <cfset tC = tC & crlf & '</cfcomponent>''>     
     <cfset ArrayAppend(arrTestCaseNames,strTestCaseName)>
     <!--- write test case  --->
     <cffile action="WRITE" file="#strTestCaseFileName#" output="#tc#" addnewline="No">     
     <cfoutput>generated last case file: #strTestCaseName#<br></cfoutput>
    </cfif> <!--- public function  --->

    </cfloop> <!--- loop through functions  --->
   
   <!--- generate test suite file  --->
   <cfset genTestSuite()>
   <cfset blnComplete = true>
  </cfif>
  
  <hr>
 
 
  <cfcatch type="Any">
   <font color="red" size="+2">
   There was an error. Please ensure that your component is located in the correct directory: <BR>
   </font>
   <table cellspacing="2" cellpadding="2" border="1">
    <tr>
     <td><cfdump var="#cfcatch#">    
     </td>
    </tr>    
    </table>
   <HR color="#ff0000" noshade>
  </cfcatch>
 
 </cftry>

 
</CFIF>


<cffunction name="genTestSuite">
 <cfset var tcName ="">
 <cfset tf = crlf> 
 <cfset tf=tf & crlf & '<cfparam name="URL.output" default="extjs">''>
 <cfset tf=tf & crlf & '<cfset testSuitePath = "mxunit.framework.TestSuite" >''>
 <cfset tf=tf & crlf & '<cfset testSuite = createObject("component", testSuitePath).TestSuite() >''>
 
 <cfloop from="1" to="#ArrayLen(arrTestCaseNames)#" index="idxArr"> 
  <cfset tcName = arrTestCaseNames[idxArr]>
  <cfset tf=tf & crlf &   '<cfset uTest = createObject("component", "#strTestCaseDirName#.#tcName#")>''>
  <cfset tf=tf & crlf &   '<cfset testSuite.addAll("#tcName#", uTest) >''>
  <cfset tf=tf & crlf>
 </cfloop>
 
 <cfset tf=tf & crlf>
 <cfset tf=tf & crlf & '<cfset results = testSuite.run() >''>
 <cfset tf=tf & crlf & '<cfset out = results.getResultsOutput(URL.output)>''>
 <cfset tf=tf & crlf & '<cfif NOT IsSimpleValue(out)>''>
 <cfset tf=tf & crlf & tab & '<cfdump var="##out##">''>   
 <cfset tf=tf & crlf & '<cfelse>''>
 <cfset tf=tf & crlf & tab & '<cfoutput>'##out##</cfoutput>'>
 <cfset tf=tf & crlf & '</cfif>''>



 
 <!--- loop through and add tests (bsoylu 03-29-2012) ---> 
 <cffile action="WRITE" output="#tf#" file="#strTestSuitePath#" addnewline="No">

</cffunction>

<cffunction name="genStartTc">
 <cfargument name="strModifier" type="string" default="" hint="modifier for component name">
 
 <!--- start testCase tC  --->
 <cfset tC = '<cfcomponent displayname="MxunitTestCase_#strTestCaseName##Arguments.strModifier#" extends="mxunit.framework.TestCase">''>
 
 <!--- empty call no parameters  --->
 <cfif Arguments.strModifier IS "">
  <cfset tc=tc & crlf & tab & '<cffunction name="testNoParams_#strMethod#" >''>  
  <cfset tc=tc & crlf & tab & tab & '<cfset var response ="">''>  
  <cfset tc=tc & crlf & tab & tab & '<cfinvoke component="#strName#" method="#strMethod#" returnvariable="response"/>''>
  <cfset tc=tc & crlf & tab & tab & '<cfset assertNotEquals( "",response,"No parameter call failed")>''>
  <cfset tc=tc & crlf & tab & '</cffunction>''>
 </cfif>
 
</cffunction>

<cfif IsDefined("blnComplete") and blnComplete>
 <font size="+2" color="#008000">
 <cfoutput>
 Successfully generated Test Suite for Component [#UCase(Form.objectName)#]. <BR>
 Test suite file : <a href="#strDirPrefix#/#strTestSuiteName#" target="_blank" title="run tests">[#strTestSuitePath#]</a> <BR>
 
 Please review and adjust specific test cases.
 </cfoutput>
 </font>
 <cfabort>
</cfif>

<h2>Welcome</h2>






 <BR>
 This program will help you generate mxUnit test cases.
 It requires access to components to be analyzed.
 <BR>
 
 After initial generation you should make changes as the assertion are generic.
 The objective is provide broad test coverage.
 
 <HR>
 Behavior Notes <BR>
 <PRE>
 All files will be generated in the "unitTests" subdirectory based on the initial directory this template is run from
 Will attempt to generate all permutations possible.
 Random values
 Break Values
 
 Test File Name:
 &nbsp;&nbsp;TestCases_[componentName]\Test_[componentName]_[functionName].cfc
 If multiple files are generated per function, a File[n] postfix wil be added except the last one:
 &nbsp;&nbsp;TestCases_[componentName]\Test_[componentName]_[functionName]_File[n].cfc
 
 An overall TestSuite will be generated
 Test Suite name:
 TestSuite_[componentName].cfm
 
 </PRE>
 
 <form method="post" action="GenerateTestsPublic.cfm">
 <table cellspacing="2" cellpadding="2" border="0">

 <tr>
  <td>Component Path from the webroot (e.g. component located [root]/myweb/cfcs/foo.cfc would be myweb.cfcs.foo)</td>
  <td><input type="Text" name="objectName" value=""></td>
 </tr>


 <tr>
  <td>Max Number of Test Cases per File (same function)</td>
  <td><input type="Text" name="maxTestsPerFile" value="1000"> (multiple files will be generated if more than this number)</td>
 </tr> 
 <tr>
  <td>Max Number of Test Cases for a Function</td>
  <td><input type="Text" name="maxTests" value="5000"></td>
 </tr>  
 <tr>
  <td></td>
  <td><input type="submit" name="submit" value="submit"></td>
 </tr> 
 </table>
 </form>



</body>
</html>





Cheers,
B.

Tuesday, July 17, 2012

CF: ColdFusion 10 on Tomcat and the CGI mistery

One of the things I applaud Adobe for in the new release of ColdFusion is to have the stomach to change the  underlying Application container. Moving from JRUN to Tomcat is the right step. However, as part of the move, there still seems to be some mystery surrounding what this means.

As pointed out previously by myself, Rupesh Kumar, has published a good primer on the differences, but also introduced some questions which I did not find answers for. So nothing like the present to go and dig.

More specifically he points out that Adobe has made changes (enhancements) to Tomcat that allow it to work hand in hand with their connectors to get more data to the servlet engine; this, according to him, would not be available if you ran a deployment of CF10 on standard Tomcat.

Of course, I wondered, what in particular would not be available and ran some tests. I used the following scenarios (all on Windows 2008 R2):

a) Standard Install of CF 10 with Adobe ISAPI connector and IIS.
b) Standard Install of CF 10 with BonCode connector and IIS.
c) Tomcat WAR deployment of CF with BonCode connector and IIS.
d) Tomcat WAR Deployment using Tomcat web server (Coyote)

I configured the BonCode connector for best alignment with Adobe as outlined in my previous blog post.
I admit that I did not try to use the Apache Tomcat  regular ISAPI redirector, since it would have not worked for scenario a) and b) and I assume that there are differences there. I also did not try Apache HTTPD webserver scenarios.


I arrived at my conclusions by using a sophisticated setup ;o)
I simply dumped the CGI scope and the count of CGI variables, then compare the dump files. The dump files in the download  zip document reflect the output for the scenarios above.


However, overall I was not able to discover large differences in the CGI scope among the different approaches for running ColdFusion 10. The results for scenarios a) - c) were identical, and while scenario d) showed differences, they were minimal. I attribute this to Tomcat's native web-server's interpretation of HTTP protocol. It chooses to interpret traffic and headers slightly differently.

The differences in scenario d) relate to the following  CGI variables:
Content_Length
Context_Path
Gateway_Interface
HTTPS


Thus, if you wish to keep your coding the same, select options a)-c) while exercising some care in the use of Apache Coyote as webserver for ColdFusion 10.

Cheers,
B.