Code Monkey home page Code Monkey logo

testcl's Introduction

Introduction

TesTcl is a Tcl library for unit testing iRules which are used when configuring F5 BIG-IP devices.

News

  • 4th May 2020 - Version 1.0.14 released
  • 10th November 2018 - Version 1.0.13 released
  • 26th September 2018 - Version 1.0.12 released
  • 24th May 2018 - Version 1.0.11 released
  • 23rd March 2017 - Version 1.0.10 released
  • 29th April 2016 - Version 1.0.9 released

Getting started

If you're familiar with unit testing and mocking in particular, using TesTcl should't be to hard. Check out the examples below:

Simple example

Let's say you want to test the following simple iRule found in simple_irule.tcl:

rule simple {

  when HTTP_REQUEST {
    if { [HTTP::uri] starts_with "/foo" } {
      pool foo
    } else {
      pool bar
    }
  }

  when HTTP_RESPONSE {
    HTTP::header remove "Vary"
    HTTP::header insert Vary "Accept-Encoding"
  }

}

Now, create a file called test_simple_irule.tcl containing the following lines:

package require -exact testcl 1.0.14
namespace import ::testcl::*

# Comment in to enable logging
#log::lvSuppressLE info 0

it "should handle request using pool bar" {
  event HTTP_REQUEST
  on HTTP::uri return "/bar"
  endstate pool bar
  run simple_irule.tcl simple
}

it "should handle request using pool foo" {
  event HTTP_REQUEST
  on HTTP::uri return "/foo/admin"
  endstate pool foo
  run simple_irule.tcl simple
}

it "should replace existing Vary http response headers with Accept-Encoding value" {
  event HTTP_RESPONSE
  verify "there should be only one Vary header" 1 == {HTTP::header count vary}
  verify "there should be Accept-Encoding value in Vary header" "Accept-Encoding" eq {HTTP::header Vary}
  HTTP::header insert Vary "dummy value"
  HTTP::header insert Vary "another dummy value"
  run simple_irule.tcl simple
}

Installing JTcl including jtcl-irule extensions

Install JTcl

Download JTcl, unzip it and add it to your path.

Add jtcl-irule to your JTcl installation

Add the jtcl-irule extension to JTcl. If you don't have the time to build it yourself, you can download the jar artifact from the release v 0.9 page or you can use the direct link. Next, copy the jar file into the directory where you installed JTcl. Add jtcl-irule to the classpath in jtcl or jtcl.bat. IMPORTANT! Make sure you place the jtcl-irule-0.9.jar on the classpath before the standard jtcl-.jar

MacOS X and Linux

On MacOs X and Linux, this can be achieved by putting the following line just above the last line in the jtcl shell script

export CLASSPATH=$dir/jtcl-irule-0.9.jar:$CLASSPATH
Windows

On Windows, modify the following line in jtcl.bat from

set cp="%dir%\jtcl-%jtclver%.jar;%CLASSPATH%"

to

set cp="%dir%\jtcl-irule-0.9.jar;%dir%\jtcl-%jtclver%.jar;%CLASSPATH%"
Verify installation

Create a script file named test_jtcl_irule.tcl containing the following lines

if {"aa" starts_with "a"} {
  puts "The jtcl-irule extension has successfully been installed"
}

and execute it using

jtcl test_jtcl_irule.tcl

You should get a success message. If you get a message saying syntax error in expression ""aa" starts_with "a"": variable references require preceding $, jtcl-irule is not on the classpath before the standard jtcl-.jar. Please review instructions above.

Add the testcl library to your library path

Download latest TesTcl distribution from github containing all the files (including examples) found in the project. Unzip, and add unzipped directory to the TCLLIBPATH environment variable:

On MacOS X and Linux:

export TCLLIBPATH=whereever/TesTcl-1.0.14

On Windows, create a System Variable named TCLLIBPATH and make sure that the path uses forward slashes '/'

In order to run this example, type in the following at the command-line:

>jtcl test_simple_irule.tcl

This should give you the following output:

**************************************************************************
* it should handle request using pool bar
**************************************************************************
-> Test ok

**************************************************************************
* it should handle request using pool foo
**************************************************************************
-> Test ok

**************************************************************************
* it should replace existing Vary http response headers with Accept-Encoding value
**************************************************************************
verification of 'there should be only one Vary header' done.
verification of 'there should be Accept-Encoding value in Vary header' done.
-> Test ok

Explanations

  • Require the testcl package and import the commands and variables found in the testcl namespace to use it.
  • Enable or disable logging
  • Add the specification tests
    • Describe every it statement as precisely as possible. It serves as documentation.
    • Add an event . This is mandatory.
    • Add one or several on statements to setup expectations/mocks. If you don't care about the return value, return "".
    • Add an endstate. This could be a pool, HTTP::respond, HTTP::redirect or any other function call (see link).
    • Add a verify. The verifications will be run immediately after the iRule execution. Describe every verification as precisely as possible, add as many verifications as needed in your particular test scenario.
    • Add an HTTP::header initialization if you are testing modification of HTTP headers (stubs/mocks are provided for all commands in HTTP namespace).
    • Add a run statement in order to actually run the Tcl script file containing your iRule. This is mandatory.
A word on the TesTcl commands
  • it statement takes two arguments, description and code block to execute as test case.
  • event statement takes a single argument - event type. Supported values are all standard HTTP, TCP and IP events .
  • on statement has the following syntax: on ... (return|error) result
  • endstate statement accepts 2 to 5 arguments which are matched with command to stop processing iRule with success in test case evaluation.
  • verify statement takes four arguments. Syntax: verify "DESCRIPTION" value CONDITION {verification code}
    • description is displayed during verification execution
    • value is expected result of verification code
    • condition is operator used during comparison of value with code result (ex. ==, !=, eq).
    • verification_code is code to evaluate after iRule execution
  • run statement takes two arguments, file name of iRule source and name of iRule to execute
A word on stubs or mockups (you choose what to call 'em)#####
HTTP namespace

Most of the other commands in the HTTP namespace have been implemented. We've done our best, but might have missed some details. Look at the sourcecode if you wonder what is going on in the mocks. In particular, the HTTP::header mockup implementation should work as expected. However insert_modssl_fields subcommand is not supported in current version.

URI namespace

Everything should be supported, with the exception of:

which is only partially supported.

GLOBAL namespace

Support for

Avoiding code duplication using the before command

In order to avoid code duplication, one can use the before command. The argument passed to the before command will be executed before the following it specifications.

NB! Be carefull with using on commands in before. If there will be another definition of the same expectation in it statement, only first one will be in use (this one set in before).

Using the before command, test_simple_irule.tcl can be rewritten as:

package require -exact testcl 1.0.14
namespace import ::testcl::*

# Comment in to enable logging
#log::lvSuppressLE info 0

before {
  event HTTP_REQUEST
}

it "should handle request using pool bar" {
  on HTTP::uri return "/bar"
  endstate pool bar
  run simple_irule.tcl simple
}

it "should handle request using pool foo" {
  on HTTP::uri return "/foo/admin"
  endstate pool foo
  run simple_irule.tcl simple
}

it "should replace existing Vary http response headers with Accept-Encoding value" {
  # NB! override event type set in before
  event HTTP_RESPONSE

  verify "there should be only one Vary header" 1 == {HTTP::header count vary}
  verify "there should be Accept-Encoding value in Vary header" "Accept-Encoding" eq {HTTP::header Vary}
  HTTP::header insert Vary "dummy value"
  HTTP::header insert Vary "another dummy value"
  run irules/simple_irule.tcl simple
}

On a side note, it's worth mentioning that there is no after command, since we're always dealing with mocks.

Advanced example

Let's have a look at a more advanced iRule (advanced_irule.tcl):

rule advanced {

  when HTTP_REQUEST {

    HTTP::header insert X-Forwarded-SSL true

    if { [HTTP::uri] eq "/admin" } {
      if { ([HTTP::username] eq "admin") && ([HTTP::password] eq "password") } {
        set newuri [string map {/admin/ /} [HTTP::uri]]
        HTTP::uri $newuri
        pool pool_admin_application
      } else {
        HTTP::respond 401 WWW-Authenticate "Basic realm=\"Restricted Area\""
      }
    } elseif { [HTTP::uri] eq "/blocked" } {
      HTTP::respond 403
    } elseif { [HTTP::uri] starts_with "/app"} {
      if { [active_members pool_application] == 0 } {
        if { [HTTP::header User-Agent] eq "Apache HTTP Client" } {
          HTTP::respond 503
        } else {
          HTTP::redirect "http://fallback.com"
        }
      } else {
        set newuri [string map {/app/ /} [HTTP::uri]]
        HTTP::uri $newuri
        pool pool_application
      }
    } else {
      HTTP::respond 404
    }

  }

}

The specs for this iRule would look like this:

package require -exact testcl 1.0.14
namespace import ::testcl::*

# Comment out to suppress logging
#log::lvSuppressLE info 0

before {
  event HTTP_REQUEST
}

it "should handle admin request using pool admin when credentials are valid" {
  HTTP::uri "/admin"
  on HTTP::username return "admin"
  on HTTP::password return "password"
  endstate pool pool_admin_application
  run irules/advanced_irule.tcl advanced
}

it "should ask for credentials when admin request with incorrect credentials" {
  HTTP::uri "/admin"
  HTTP::header insert Authorization "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
  verify "user Aladdin" "Aladdin" eq {HTTP::username}
  verify "password 'open sesame'" "open sesame" eq {HTTP::password}
  verify "WWW-Authenticate header is 'Basic realm=\"Restricted Area\"'" "Basic realm=\"Restricted Area\"" eq {HTTP::header "WWW-Authenticate"}
  verify "response status code is 401" 401 eq {HTTP::status}
  run irules/advanced_irule.tcl advanced
}

it "should ask for credentials when admin request without credentials" {
  HTTP::uri "/admin"
  verify "WWW-Authenticate header is 'Basic realm=\"Restricted Area\"'" "Basic realm=\"Restricted Area\"" eq {HTTP::header "WWW-Authenticate"}
  verify "response status code is 401" 401 eq {HTTP::status}
  run irules/advanced_irule.tcl advanced
}

it "should block access to uri /blocked" {
  HTTP::uri "/blocked"
  endstate HTTP::respond 403
  run irules/advanced_irule.tcl advanced
}

it "should give apache http client a correct error code when app pool is down" {
  HTTP::uri "/app"
  on active_members pool_application return 0
  HTTP::header insert User-Agent "Apache HTTP Client"
  endstate HTTP::respond 503
  run irules/advanced_irule.tcl advanced
}

it "should give other clients then apache http client redirect to fallback when app pool is down" {
  HTTP::uri "/app"
  on active_members pool_application return 0
  HTTP::header insert User-Agent "Firefox 13.0.1"
  verify "response status code is 302" 302 eq {HTTP::status}
  verify "Location header is 'http://fallback.com'" "http://fallback.com" eq {HTTP::header Location}
  run irules/advanced_irule.tcl advanced
}

it "should give handle app request using app pool when app pool is up" {
  HTTP::uri "/app/form?test=query"
  on active_members pool_application return 2
  endstate pool pool_application
  verify "result uri is /form?test=query" "/form?test=query" eq {HTTP::uri}
  verify "result path is /form" "/form" eq {HTTP::path}
  verify "result query is test=query" "test=query" eq {HTTP::query}
  run irules/advanced_irule.tcl advanced
}

it "should give 404 when request cannot be handled" {
  HTTP::uri "/cannot_be_handled"
  endstate HTTP::respond 404
  run irules/advanced_irule.tcl advanced
}

stats

Modification of HTTP headers example

Let's have a look at another iRule (headers_irule.tcl):

rule headers {

  #notify backend about SSL using X-Forwarded-SSL http header
  #if there is client certificate put common name into X-Common-Name-SSL http header
  #if not make sure X-Common-Name-SSL header is not set
  when HTTP_REQUEST {
    HTTP::header insert X-Forwarded-SSL true
    HTTP::header remove X-Common-Name-SSL
    
    if { [SSL::cert count] > 0 } {
      set ssl_cert [SSL::cert 0]
      set subject [X509::subject $ssl_cert]
      set cn ""
      foreach { label value } [split $subject ",="] {
        set label [string toupper [string trim $label]]
        set value [string trim $value]
        
        if { $label == "CN" } {
          set cn "$value"
          break
        }
      }
    
      HTTP::header insert X-Common-Name-SSL "$cn"
    }
  }

}

The example specs for this iRule would look like this:

package require -exact testcl 1.0.14
namespace import ::testcl::*

# Comment out to suppress logging
#log::lvSuppressLE info 0

before {
  event HTTP_REQUEST
  verify "There should be always set HTTP header X-Forwarded-SSL to true" true eq {HTTP::header X-Forwarded-SSL}
}

it "should remove X-Common-Name-SSL header from request if there was no client SSL certificate" {
  HTTP::header insert X-Common-Name-SSL "testCommonName"
  on SSL::cert count return 0
  verify "There should be no X-Common-Name-SSL" 0 == {HTTP::header exists X-Common-Name-SSL}
  run irules/headers_irule.tcl headers
}

it "should add X-Common-Name-SSL with Common Name from client SSL certificate if it was available" {
  on SSL::cert count return 1
  on SSL::cert 0 return {}
  on X509::subject [SSL::cert 0] return "CN=testCommonName,DN=abc.de.fg"
  verify "X-Common-Name-SSL HTTP header value is the same as CN" "testCommonName" eq {HTTP::header X-Common-Name-SSL}
  run irules/headers_irule.tcl headers
}

Classes Example

TesTcl has partial support for the class command. For example, we could test the following rule:

rule classes {
  when HTTP_REQUEST {
    if { [class match [IP::remote_addr] eq blacklist] } {
      drop
    } else {
      pool main-pool
    }
  }
}

with code that looks like this

package require -exact testcl 1.0.14
namespace import testcl::*

before {
  event HTTP_REQUEST
  class configure blacklist {
    "192.168.6.66" "blacklisted"
  }
}

it "should drop blacklisted addresses" {
  on IP::remote_addr return "192.168.6.66"
  endstate drop
  run irules/classes.tcl classes
}

it "should not drop addresses that are not blacklisted" {
  on IP::remote_addr return "192.168.0.1"
  endstate pool main-pool
  run irules/classes.tcl classes
}

How stable is this code?

This work is quite stable, but you can expect minor breaking changes.

Why I created this project

Configuring BIG-IP devices is no trivial task, and typically falls in under a DevOps kind of role. In order to make your system perform the best it can, you need:

  • In-depth knowledge about the BIG-IP system (typically requiring at least a $2,000 3-day course)
  • In-depth knowledge about the web application being load balanced
  • The Tcl language and the iRule extensions
  • And finally: A way to test your iRules

Most shops test iRules manually, the procedure typically being a variation of the following:

  • Create/edit iRule
  • Add log statements that show execution path
  • Push iRule to staging/QA environment
  • Bring backend servers up and down manually as required to test fallback scenarios
  • Generate HTTP-traffic using a browser and verify manually everything works as expected
  • Verify log entries manually
  • Remove or disable log statements
  • Push iRule to production environment
  • Verify manually everything works as expected

There are lots of issues with this manual approach:

  • Using log statements for testing and debugging messes up your code, and you still have to look through the logs manually
  • Potentially using different iRules in QA and production make automated deployment procedures harder
  • Bringing servers up and down to test fallback scenarios can be quite tedious
  • Manual verification steps are prone to error
  • Manual testing takes a lot of time
  • Development roundtrip-time is forever, since deployment to BIG-IP sometimes can take several minutes

Clearly, manual testing is not the way forward!

Test matrix and compatibility

Mac Os X Windows Cygwin
JTcl 2.4.0 yes yes yes
JTcl 2.5.0 yes yes yes
JTcl 2.6.0 yes yes yes
JTcl 2.7.0 yes yes yes
JTcl 2.8.0 yes yes yes
Tclsh 8.6 yes* yes* ?

The * indicates support only for standard Tcl commands

If you use TesTcl on a different platform, please let us know

Getting help

Post questions to the group at TesTcl user group
File bugs over at github

Contributing code

See CONTRIBUTING.md

Who uses it?

Well, I can't really tell you, but according to Google Analytics, this site gets around 10 hits per day.

License

Just like JTcl, TesTcl is licensed under a BSD-style license.

Please please please

Drop me a line if you use this library and find it useful: stefan.landro you know what gmail.com

You can also check out my LinkedIn profile or my Google+ profile, or even my twitter account - follow it for TesTcl releases

testcl's People

Contributors

ancampbell-au avatar gszeliga-nr avatar jamespic avatar jonwolski avatar kugg avatar landro avatar magnuswatn avatar racke avatar sebastian-brzuzek avatar wulczer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

testcl's Issues

linting of the test files?

Are there any plans to implement linting of the test files?

sort of like:

  • every it "should" {} block should contain a run command
  • every it "should" {} block should contain at least one of either verify or endstate
  • every file should end with stats (to exit with the correct error code)

Implementing K23237429

New feature idea. Error checking according to K23237429
K23237429: TCL error: ERR_NOT_SUPPORTED after upgrade to version 14.1.0 or later

After version 14.1.0 the new error 01220001:3: TCL error: /Common/<iRule_name> <HTTP_REQUEST> - ERR_NOT_SUPPORTED (line 1) invoked from within "HTTP::host" exist.

HTTP iRule commands that inspect HTTP state after the commands HTTP::respond, HTTP::redirect and HTTP::retry will now return errors instead of returning corrupt data.

Can testcl set HTTP::has_responded and return error if the iRule is trying to utilize HTTP context commands after already sending an HTTP response or redirect.

Error using HTTP::cookie

"HTTP::cookie command is not implemented - use on HTTP::cookie..."

Is this not handled currently? Any plans on it?

URI:: functionality support

I would like request the URI namespace functionality support in TesTcl.

URI::basename - Extracts the basename part of a given uri string.
URI::compare - Compares two URI's for equality.
URI::decode - Returns a decoded version of a given URI.
URI::encode - Returns an encoded version of a given URI.
URI::host - Returns the host portion of a given URI.
URI::path - Returns the path portion of the given URI.
URI::port - Returns the host port from the given URI.
URI::protocol - Returns the protocol of the given URI.
URI::query - Returns the query string portion of the given URI or the value of a query string parameter.

Setting Procedure Variables

I would like to be able to set variables in either before or it to set up the expected environment for my rule execution. I'm having issues determining how to do this, but it may be my lack of TCL expertise:

Given:

  • I want to test with event HTTP_REQUEST
  • In my actual iRule I need access to the request's port, which I can get at during the CLIENT_ACCEPTED event
  • TesTcl does not support the CLIENT_ACCEPTED event or chaining of events
  • I would like to be able to set orig_port from inside before or it blocks so that it is available during execution of the HTTP_REQUEST event
when CLIENT_ACCEPTED {
  set orig_port "[TCP::local_port]"
}
when HTTP_REQUEST {
  # Set x-forwarded-port Header
  if {[info exists orig_port]} {
    if { [HTTP::header exists "x-forwarded-port"] } {
      HTTP::header replace "x-forwarded-port" "$orig_port"
    } else {
      HTTP::header insert "x-forwarded-port" "$orig_port"
    }
  }

I have tried set orig_port "80" in before and it. I have tried on orig_port return "80". None of my approaches have worked. How can I set variables at the scope of the rule procedure?

Thanks for the help!

testing elseif/else conditions failure

Hi. Thanks for developing the great tool.
I faced weird error but I'm not sure it's right behavior or not.

Here is a simple iRule else.tcl that I want to apply test:

rule else_test {
    when CLIENT_ACCEPTED {
        if { [IP::client_addr] equals "192.168.0.1"} {
            pool hoge
        }
        elseif { [IP::client_addr] equals "192.168.100.1"} {
            pool fuga
        }
    }
}

I want to test both condition (if and elseif) with one testcl file, so I wrote test_else.tcl like

package require -exact testcl 1.0.14
namespace import ::testcl::*

it "should handle request using pool hoge" {
    event CLIENT_ACCEPTED
    on IP::client_addr return "192.168.0.1"
    endstate pool hoge
    run else.tcl else_test
}

it "should handle request using pool fuga" {
    event CLIENT_ACCEPTED
    on IP::client_addr return "192.168.100.1"
    endstate pool fuga
    run else.tcl else_test
}

However, it will produce an error on the second test.

jtcl .\test_else.tcl

**************************************************************************
* it should handle request using pool hoge
**************************************************************************
-> Test ok

**************************************************************************
* it should handle request using pool fuga
**************************************************************************

Unexpected unknown command invocation 'elseif { [IP::client_addr] equals "192.168.100.1"} {
            pool fuga
        }'

Maybe you should add an "on" statement similar to the one below to your "it" block?

    it "your description" {
      ...
      on elseif { [IP::client_addr] equals "192.168.100.1"} {
            pool fuga
        } return "your return value"
      ...
    }

error     Expected end state with return code 3, got 1
error     Error info: Unexpected unknown command invocation 'elseif { 
[IP::client_addr] equals "192.168.100.1"} {
error                 pool fuga
error             }'
error         while executing
error     "error $errorMessage"
error         ("if" then script line 11)
error         invoked from within
error     "if {$rc == 1500} {
error         #expectation and end state not found
error         set errorMessage "Unexpected unknown command invocation 
'$args'"
error         puts "\n$errorMes..."
error         (procedure "::unknown" line 9)
error         invoked from within
error     "elseif { [IP::client_addr] equals "192.168.100.1"} {       
error                 pool fuga
error             }"
error     ++++++++++++++++++++++++++++++++++++++++++
error     Expected return code 200 from calling when, got 1
error     Error info: Expected end state with return code 3, got 1
error         while executing
error     "error "Expected end state with return code 3, got $rc""
error         ("if" then script line 5)
error         invoked from within
error     "if {$rc != 1000} {
error           log::log error "Expected end state with return code 3, got $rc"
error           log::log error "Error info: $::errorInfo"
error           log::lo..."
error         ("if" then script line 16)
error         invoked from within
error     "if {[info exists expectedEvent] && $event eq $expectedEvent} {
error         log::log debug "when invoked with expected event '$event'"
error         set rc [catch $body ..."
error         (procedure "when" line 20)
error         invoked from within
error     "when CLIENT_ACCEPTED {
error             if { [IP::client_addr] equals "192.168.0.1"} {
error                 pool hoge
error             }
error             elseif { [IP::client_addr] equal..."
error     ++++++++++++++++++++++++++++++++++++++++++
error     Running irule else.tcl failed: Expected return code 2000 from calling when, got 1
error     Error info: Expected return code 2000 from calling when, got 1
error         while executing
error     "error "Expected return code 2000 from calling when, got $rc""
error         ("if" then script line 5)
error         invoked from within
error     "if {$rc != 2000} {
error         log::log error "Expected return code 200 from calling when, got $rc"
error         log::log error "Error info: $::errorInfo"
error         log::log..."
error         (procedure "rule" line 6)
error         invoked from within
error     "rule else_test {
error         when CLIENT_ACCEPTED {
error             if { [IP::client_addr] equals "192.168.0.1"} {
error                 pool hoge
error             }
error             elseif { [I..."
error         (file "else.tcl" line 1)
error         invoked from within
error     "source $irule"
error     ++++++++++++++++++++++++++++++++++++++++++
-> Test failure!!
-> -> Running irule else.tcl failed: Expected return code 2000 from calling when, got 1
error     Running irule else.tcl failed: Expected return code 2000 from calling when, got 1

Same error occur when I use else instead of elseif to test the second condition.
I found out that it will be done successfully if I use if for each condition like:

rule else_test {
    when CLIENT_ACCEPTED {
        if { [IP::client_addr] equals "192.168.0.1"} {
            pool hoge
        }
        if { [IP::client_addr] equals "192.168.100.1"} {
            pool fuga
        }
    }
}
jtcl .\test_else.tcl

**************************************************************************
* it should handle request using pool hoge
**************************************************************************
-> Test ok

**************************************************************************
* it should handle request using pool fuga
**************************************************************************
-> Test ok

but it's not the code I want to test.
Do I have some mistake or it's right behavior?

Variable support

Hi mr. Landrø,

I'm using your excellent tool to test drive our BIG-IP development. One question on variable support:

The following (valid BIG-IP) iRule:

rule http_error {

  when HTTP_REQUEST {
    set log_level debug
    # ..
    log local0.$log_level "Request"
  }

  when HTTP_RESPONSE {
    # ..
    log local0.$log_level "Response"
    HTTP::respond 404
  }
}

When testing the event HTTP_RESPONSE, the log_level variable is never set, and thus not available in the HTTP_RESPONSE clause. Is there a way to achieve this?

package require -exact testcl 1.0.7
namespace import ::testcl::*

it "should respond with 404" {
  event HTTP_RESPONSE
  endstate HTTP::respond 404
  run irules/http_error_irule.tcl http_error
}

stats
error     Expected end state with return code 3, got 1
error     Error info: can't read "log_level": no such variable
error         while executing
error     "log local0.$log_level "Response""

Is there any way I can use variables, or does the test runner currently not support this?

Regards,
Snorre

Update doc

Update download link to releases at github
Clean up documentation

Return ok with failed tests

It looks like it returns OK even if I get failed test on verification. Can I make it return error on verification failure.

it "should allow casino10-qa.test.example.com" {
  source anon-hostheader_class.tcl

  on IP::client_addr return "1.2.3.4" 
  on HTTP::host return "casino10-qa.test.example.com"
  on HTTP::uri return "/init"
  verify "service-gateway-qa-gateway-anon.ocptest.example.com" "service-gateway-qa-ERROR-anon.ocptest.example.com" == {HTTP::header Host}
  endstate pool ocp_dev_infranodes_443
  run test_suppliers.irule test.suppliers-irule
}

And results

 **************************************************************************
* it should allow casino10-qa.test.example.com
**************************************************************************
-> Test failure!!
-> -> Verification 'service-gateway-qa-gateway-anon.ocptest.example.com' failed - expression: {service-gateway-qa-ERROR-anon.ocptest.example.com} == {service-gateway-qa-gateway-anon.ocptest.example.com}
error     Verification 'service-gateway-qa-gateway-anon.ocptest.example.com' failed - expression: {service-gateway-qa-ERROR-anon.ocptest.example.com} == {service-gateway-qa-gateway-anon.ocptest.example.com}
(venv) [olanys@netman02 test-f5-config]$ echo $?
0

As you can see we get 0 (ok) as returncode, and so the test-suite says everything is OK.

Error if test file is not found

If you pass a non-existent file to jtcl or you pass in a file that passes all tests, you get the same output (none). This can mislead you into thinking your tests pass when in reality you're just not running them. If you pass in a file that does not exist, it should error.

I suspect this requires a change to jtcl. Is there some form of workaround?

event priority support

Hi, I would like to request the ability to support events with priorities, e.g.:

when <event_name> priority nnn

As per F5 documentation priorit could be a number between 1-1000 (500 is the default value if priority is omitted)

Here is what happens when I try to test an iRule with priority set in TesTcl 1.0.5:

error Expected return code 200 from calling when, got 1
error Error info: wrong # args: should be "when event body"
error while executing
error "when HTTP_REQUEST priority 100 {

TesTcl should understand if there are multiple event handlers for an event (e.g HTTP_REQUEST) with different priorities and handle the test cases correctly in the order of priority

Verify irule extension is loaded

Provide better error messages to users that use either jtcl or tclsh and potentially inform them om a missing jtcl-irule extension

iRule problem

Hi all,

I have to test my iRule using this tool.
When i run this command "jtcl test_jtcl_irule.tcl" the output is correct ("The jtcl-irule extension has successfully been installed") so i have installed correctly the tcl on my mac.
The problem is reflected in the next step...
I have the following problems:

  1. I can't find the file specified ("test_simple_irule.tcl") on the directory downloaded here.
  2. if I try to run other irules I get compilation errors as if it didn't recognize the language (iRule)
    ex:
    iRule test.tcl:
    when HTTP_REQUEST { if { [HTTP::host] starts_with "fir3net.com" } { HTTP::redirect http://www.fir3net.com[HTTP::uri] } }

i run jtcl test.tcl and i got this error:

invalid command name "when"

Someone could help me?
Thanks

Helpers for mocking and verifying statuscodes, headers etc.

I've created some procedures for mocking headers and uri's, and for verifying status codes and return headers. When I write a lot of tests, this makes them easier to read.

Question 1:
Is there a possibility that these types of utility functions could be included in the TesTcl core?

Question 2:
One of my utility functions (the one named "verifyHeader" below) isn't working, it's producing this error:

Unexpected error during invocation of "verify 'Header 'Content-Type' should be 'text/plain''": rc=1, res=can't read "headerName": no such variable

Any idea what can be the cause of this error? I guess it's might be just some TCL syntax error on my part...

This is the test I've written with the procedures, and one of the tests (Isolated the procedures might seem overkill, but I've got 10-15 more of these tests...)

package require -exact testcl 1.0.2
namespace import ::testcl::*

# Comment in to enable logging
#log::lvSuppressLE info 0

proc mockUri {mockUrl} {
    on HTTP::uri return $mockUrl
}
proc mockHeader {headerName headerValue} {
    on HTTP::header $headerName return $headerValue
}

proc verifyStatusCode {statusCode} {
    verify "Status code is '$statusCode'" $statusCode eq {HTTP::status}
}
proc verifyHeader {headerName expectedHeaderValue} {
    # This one doesn't work...
    verify "Header '$headerName' should be '$expectedHeaderValue'" $expectedHeaderValue eq {HTTP::header $headerName}
}

before {
    event HTTP_REQUEST
}

it "should remove '/api' from URI" {
    mockUri "/api/users"
    mockHeader "Accept" "application/json"
    on active_members pool_api return 0

    verifyStatusCode 503
    verifyHeader "Content-Type" "text/plain"
    verifyHeader "Cache-Control" "no-cache"
    verifyHeader "X-Forwarded-SSL" "true"

    run rules/2.tcl http
}

This is the iRule:

rule http {
    when HTTP_REQUEST {
        HTTP::header insert X-Forwarded-SSL true

        if { [HTTP::uri] starts_with "/api/"} {
            if { [active_members pool_api] == 0 } {
                if { [HTTP::header Accept] contains "json" } {
                    HTTP::respond 503 content "System not availiable" Content-Type "text/plain" Cache-Control "no-cache"
                } else { 
                    HTTP::redirect "http://errorsite.com" 
                }
            } else {
                set newuri [string map {/api/ /} [HTTP::uri]]
                HTTP::uri $newuri
                pool pool_api
            }
        } else {
            if { [active_members pool_backend] == 0 } {
                HTTP::redirect "http://errorsite.com"
            } else {
                pool pool_backend
            }
        }
    }
}

Redundancy In `On` Setup Statements

Again, this may simply be my lack of TCL knowledge here, but I have a number of log local0. statements in my iRule (e.g. https://github.com/pivotalservices/pivotal-cf-irules/blob/master/irules/pcf_rewrite_rule.tcl#L35).

I would ideally like to just have one on statement in the before which accepts a variable amount of text and throws it away. I cannot figure out how to do this, so I am left with four statements that vary for each test: https://github.com/pivotalservices/pivotal-cf-irules/blob/master/test/test_pcf_rewrite_rule_request.tcl#L18-L21

on log local0. {Received Incoming Request: http://my.company.com/spring-music} return ""
on log local0. {Rewriting Incoming Request: http://my.company.com/spring-music} return ""
on log local0. {Parsed Incoming Request: Route Name – spring-music, URI – } return ""
on log local0. {Redirecting http://my.company.com/spring-music to http://my.company.com/spring-music/} return ""

As you can see, it sure would be great if i could instead have the following in before:

on log local0. {} return ""

Any ideas on how I can reduce the boilerplate associated with this?

Overwriting Fields in iRule

I am trying to verify that a URI rewrite is occuring -- Is it expected that you are unable to overwrite a field that you already set in the test with an iRule? For example:

iRule:

rule simple {

  when HTTP_REQUEST {
    HTTP::uri "/foobar"
  }

}

Test:

it "rewrite example" {
  event HTTP_REQUEST
  on HTTP::uri return "/foo"
  verify "URI is rewritten to" "/foobar" eq {HTTP::uri}
  run resources/simple_irule.tcl simple
}

in the debug, it looks like the value is getting set

debug HTTP::uri set: /foobar

However, when the verify executes it goes back to what was set in the on

info Returning value '/foo' for procedure call 'HTTP::uri'

error Verification 'URI is rewritten to' failed - expression: {/foobar} eq {/foo}

I know there is an issue trying to overwrite when using before statements, but wasn't sure if it was possible in the actual iRule. Any help would be much appreciated!

Return code != 0 when tests are failing

First of all, thanks for your work. TesTcl is wonderful for accelerating & testing F5 iRules 😍.

A common practice in CI pipelines is to fail build when tests are failing (on pull request for sample).
An easy way is to use the return code of test command, which is a default behavior when result is != 0.

I'm trying to achieve it without success. I agree a trick could be to analyze the output log, but less beautiful way.

Snippet for reproduction:

# File: sample.tcl
rule sample {

  when HTTP_REQUEST {
    HTTP::header replace "Some-Header" "FooBar"
  }
}
# File: test_sample.tcl
package require -exact testcl 1.0.14

namespace import ::testcl::*

it "should fail" {
  event HTTP_REQUEST
  verify "Header added" "FooBar" eq {HTTP::header "Some-Header"}
  verify "Header does not exist should fail" "No Value" eq {HTTP::header "Header-Not-Exist"}
  run sample.tcl sample
}

Command execution (Using #51, really useful ... and problem seems not linked to Docker):

$ docker run -it --rm -v $(pwd):/app jones2748/docker-testcl jtcl /app/test_sample.tcl

**************************************************************************
* it should fail
**************************************************************************
verification of 'Header added' done.
-> Test failure!!
-> -> Verification 'Header does not exist should fail' failed - expression: {No Value} eq {}
error     Verification 'Header does not exist should fail' failed - expression: {No Value} eq {}

$ echo $?
0

Is it a way to achieve it with TesTcl or the problem is in used components (like jtcl ... not active project since 7 years ... so perhaps less easy 😢).

Many thanks.

Contribution guidelines

Add some contribution guidelines

  • Run tests on jtcl which is preferred platform p.t.
  • Code style (2 spaces for tabs)
  • Add/update documentation

Verify on HTTP::uri after mocking HTTP::uri

I'm using "on" to mock an incoming URI, and then my iRule changes this URI (removing a prefix) before sending it to the backend server. I want to test that the resulting URI is right, but this is a problem since I've already mocked out "HTTP::uri".

I've solved it by doing the verify on HTTP::path instead, but that feels like a hack. Any way to solve this?

Example iRule:

rule http {
    when HTTP_REQUEST {
        set newuri [string map {/api/ /} [HTTP::uri]]
        HTTP::uri $newuri
    }
}

Example test:

package require -exact testcl 1.0.2
namespace import ::testcl::*

# Comment in to enable logging
#log::lvSuppressLE info 0

it "should remove '/api' from URI" {
    event HTTP_REQUEST

    on HTTP::uri return "/api/users"

    ## This verify doesn't work:
    verify "result uri is '/users'" "/users" eq {HTTP::uri}

    ## This is the temporary solution I've used:
    verify "result path is '/users'" "/users" eq {HTTP::path}

    run rules/1.tcl http
}

Unexpected unknown command invocation 'class configure'

Hi!
I'm trying to follow the examples for classes from the documentation, but I can't get it to work. I'm getting syntax errors:

Unexpected unknown command invocation 'class configure blacklist {
    "blacklisted" "192.168.6.66"
  }'

Maybe you should add an "on" statement similar to the one below to your "it" block?

    it "your description" {
      ...
      on class configure blacklist {
    "blacklisted" "192.168.6.66"
  } return "your return value"
      ...
    }

I'm using jtcl 2.8.0 and testcl 1.0.8 on Java 1.8.0_60, and start jtcl like this: /opt/jdk/bin/java -DTCLLIBPATH=/jtcl/TesTcl -cp /jtcl/jtcl-irule.jar:/jtcl/jtcl-2.8.0.jar tcl.lang.Shell.

Some incompatibility with my versions, or error in the invocation?

support for event context validation

Hi, I would like to request a feature to validate the commands in event context- this is something that F5 does on saving of an iRule, however TesTcl misses entirely . for example the following iRUle will fail to save on F5

rule bad_context {
when HTTP_RESPONSE {
log local.0 "URI is: [HTTP::uri]"
}

}

the error message will be "command not valid in the current event context (HTTP_RESPONSE)[HTTP::uri]

This is a must-have feature in my opinion - the above iRule must raise an error in TesTCL (currently it doesn't!).

DevCentral lists which commands are valid in which event context so I hope this isn't a difficult feature to implement

datagroup functionality support (with class command)

I would to request the data group functionality in TestTcl, which I belive shold cover the following:

  1. ability to declare the datagroup and its contents in "it" or "before" in the test file
  2. support for "class" commands in iRules e.g:

class match []
class search []
class lookup
class element []
class type
class exists
class size
class names [-nocase] []
class get [-nocase] []
class startsearch
class nextelement [] <search_id>
class anymore <search_id>
class donesearch <search_id>

Regression in error output

In 1.0.1 we used to have output like this

Unexpected unknown command invocation 'HTTP::respond 403'

Maybe you should add an "on" statement similar to the one below to your "it" block?

it "your description" {
  ...
  on HTTP::respond 403 return "your return value"
  ...
}

That just went away in 1.0.2 - @Sebastian-Brzuzek do you know what happened?

Support for virtual

As documented here:

https://clouddocs.f5.com/api/irules/virtual.html

Currently I am using

on virtual name return foo
on virtual foo return ""

But then I cannot check that the virtual server is set properly. Ideally I would like to do something like

verify "Final virtual is the expected one after a match" "foo" eq { endstate_virtual }

(if it was possible to verify the value of a variable it would be great too)
Thanks for the great tool!

Documentation should explain what you'll see if misconfigured

I'm trying to run the script that says

if {"aa" starts_with "a"} {
  puts "The jtcl-irule extension has successfully been installed"
}

But I get an error that says:

syntax error in expression ""aa" starts_with "a"": variable references require preceding $
    ("if" test expression)
    invoked from within
"if {"aa" starts_with "a"} {
  puts "The jtcl-irule extension has successfully been installed"
}"
    (file "misc/f5/irules/test/test_jtcl_irule.tcl" line 1)

Is that because I have not configured jtcl-irule.jar correctly? It would be nice if the documentation provided some troubleshooting information if someone can't get past this step.

Make stubs and mocks work together

Right now, there are conflicts between
on HTTP:header ... and the stub contributed
Would be nice if the stub could fall back to the unknown implementation.

Who uses it

Add info on who uses it to docs. Use web statistics. Please folks, if you use it, add a comment, and we'll at it to the docs.

Add test matrix to docs

Sebastian tested TesTcl with ActiveTcl 8.6 and jtcl on windows
Stefan tested with jtcl on mac os
Some testing with cygwin on windows

Stacked iRules - Global Variables

Hi,

Would it be possible to extend TesTcl to allow for stacked iRules, a simple example would be -

Rule1 { set $tempVar = PoolA }

Rule2 { $tempVar = PoolB }

Rule3 { open $tempVar }

These three rules are used to set the pool variable before it gets called in the final rule, testing Rule3 in isolation would not be a valid test for us.

Thanks,

Phil

irules withour rule name { }

I use a lot of iRules with ansibles bigip_irule and the irules and not wrapped in rule name { IRULE }
Is there a way to use testcl to test them?

Or do I have to make a builder-script to wrap the files in rule NAME {} to be able to test them?

iRule procs and call command support

I would like to request implementation of procs and "call" command. Starting from TMOS v 11.4 it is possible to define and use procs in an iRules (outside of "when" event) or in a separate iRule(not even associated with virtual server). procs defined inside iRules must be called using a new "call" command

References:

https://devcentral.f5.com/articles/getting-started-with-irules-procedures

https://devcentral.f5.com/questions/procedures-subroutines-for-irules-have-arrived-in-1140

https://devcentral.f5.com/wiki/irules.call.ashx

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.