Resip TFM

From reSIProcate
Jump to: navigation, search

Introduction[edit]

The proxy tests are in sip/tfm/sanityTests.cxx These tests exercise the SIP stack and the proxy (including the registrar). sanityTests are an excellent regression tool for checking the state of the proxy, registrar, and SIP stack. sanityTests should be run regularly and extended with new tests as necessary.

Cookbook[edit]

TFM is best understood by example. TFM is a scripting language. Although the language is embedded in C++, the results are interpreted. Remember that nothing happens until ExecuteSequences() is called.

The general form of a TFM test is:

  1. provisioning -- setting up the players and data for the test (also known as preconditions)
  2. Seq)uence
    1. starting action -- cause initial event(s)
    2. expectation
      1. matching predicates -- matched against the current event
      2. timeout -- how long to wait around for an event to match
      3. expect action (executed only if the predicates match the event) -- cause new event(s)
    3. ... -- more expectations
    4. wait time for stray events
  3. in general, there can be more than one Sequence -- they are executed in parallel
  4. ExecuteSequences()

If events occur that match each of the expectations in order and no expect action triggers a failure and no event occurs that does not match an available expectation, then the test has passed. Otherwise, the test has failed. A test can fail for a variety of reasons:

  1. an expectation times out
  2. an event does not match the available expectation(s)
  3. an event matched an available expectation but the expect action directly triggered a failure
    1. failure could have been triggered by an action that examined the event
    2. failure could have been triggered by an action that examined other state (also known as a post-condition)
  4. an event occurred after the last expectation during the wait for for stray events
  5. some predicate or action threw an exception

Example[edit]

Anatomy[edit]

void testRegisterBasic()
{
   WarningLog(<<"*!testRegisterBasic!*");

          provisioning

   TestUser jason(Uri("sip:jason@localhost"), "jason", "jason");

          sequence and first action

   Seq(jason.registerUser(60, jason.getDefaultContacts()),

                  first expectation

       jason.expect(REGISTER/407, from(proxy), 
                            WaitForResponse, 
                            jason.digestRespond()),

                  second expectation

       jason.expect(REGISTER/200, from(proxy), 
                            WaitForResponse, 
                            jason.noAction()),

                  wait for stray events

       WaitForEndOfTest);

          start the interpreter

   ExecuteSequences();
}

Detailed Anatomy[edit]

test name -- this is a method on a Fixture

void testRegisterBasic()
{

          handy to know what test is running

   WarningLog(<<"*!testRegisterBasic!*");
   

          create a test user guard for easy cleanup. you might subclass TestUser to provide specific provisioning code for the proxy you are trying to test.

   TestUser jason(Uri("sip:jason@localhost"), "jason", "jason");

     define the sequence
     |                actions defined on TestSipEndPoint, accessed by TestUser
     |                |                              attributes accessible from TestSipEndPoint
     |                |                              |

   Seq(jason.registerUser(60, jason.getDefaultContacts()),

                      expect is defined on TestSipEndPoint
                      |       event SIP type
                      |       |       predicate by operator overload
                      |       |       | event SIP response code
                      |       |       | |    from predicate
                      |       |       | |    |

       jason.expect(REGISTER/407, from(proxy), 

                              constant defined by Fixture
                              |

                            WaitForResponse, 

                                           expect action is defined on TestSipEndpoint
                                           |

                            jason.digestRespond()),
       jason.expect(REGISTER/200, from(proxy), 
                            WaitForResponse, 

                                           expect No-Op  action is defined on TestSipEndpoint
                                           |

                            jason.noAction()),

         constant defined by Fixture
         |

       WaitForEndOfTest);
   ExecuteSequences();
}

REGISTER Example[edit]

Description[edit]

  1. provision a TestUser
  2. sequence
    1. send a register message
    2. expect a digest challenge response (407), send digest authed request
    3. expect a 200 OK to the authed REGISTER

Code[edit]

void testRegisterBasic()
{
   WarningLog(<<"*!testRegisterBasic!*");
   
   TestUser jason(Uri("sip:jason@localhost"), "jason", "jason");

   Seq(jason.registerUser(60, jason.getDefaultContacts()),
       jason.expect(REGISTER/407, from(proxy), 
                            WaitForResponse, 
                            jason.digestRespond()),
       jason.expect(REGISTER/200, from(proxy), 
                            WaitForResponse, 
                            jason.noAction()),
       WaitForEndOfTest);
   ExecuteSequences();
}

Time Flow[edit]

(italics represent system under test,
bold represents sequence execution,
plain type represents framework)

  1. provisioning happens immediately (normal C++ code)
  2. sequence arguments are evalulated; the return values are descriptions of the actions and expectations
  3. initial action is stored
  4. expectation stack is created and populated with expectations in order
  5. stray events timer value is stored
  6. ExecuteSequences
    1. start action is executed -- REGISTER is sent
    2. REGISTER gets to proxy and runs features etc -- proxy sends back a 407 response
    3. 407 response is delivered to the intepreter as the event
    4. available expectations are handed the event
      1. only first expectation is available
      2. first expectation's predicate is executed against the event
        1. is it a REGISTER response? yes!
        2. is it a 407? yes!
        3. is it from the proxy (check via)? yes!
        4. expectation matches!
      3. first expectation's action is executed
        1. executes TestUser::DigestRespond::go(event) -- send REGISTER with authentication
    5. REGISTER with auth gets to proxy -- proxy sends back a 200
    6. first expectation is popped from the stack and is no longer available; second expectation becomes available
    7. 200 resonse is delivered to the interpreter as the event
    8. available expectations are handed the event
      1. only second expectation is available
      2. second expectation's predicate is executed against the event
        1. is it a REGISTER response? yes!
        2. is it a 200? yes!
        3. is it from the proxy (check via)? yes!
        4. expectation matches!
        5. second expectation's actions are executed
          1. execute no-op -- does nothing
    9. second expectation is popped from the stack and is no longer available
    10. no further expectations -- start wait for stray events timer
    11. wait for stray events timer fires
    12. test ends successfully

INVITE Example[edit]

Description[edit]

This is a two endpoint test. The system under test is the proxy and SIP stack.

  1. provision a TestUser
  2. sequence 1 (this is the usual register sequence for 102)
    1. send a register message; store the message
    2. expect a digest challenge response (407), send digest authed request
    3. expect a 200 OK to the authed REGISTER
  3. sequence 2 (the 'real' test)
  4. 103 sends an INVITE to 102 (by address of record)
  5. 103 expect the digest challenge 407; ACK the 407 and respond to the challenge with auth
  6. 102 expects the INVITE (through its registration); rings (send 180), and answers (send 200)
  7. 103 expects the 180
  8. 103 expects the 200; send the ACK completing the INVITE transaction
  9. 102 expects the ACK
  10. test ends

Code[edit]

void testInviteClientRetransmissionsWithRecovery()
{
   WarningLog(<<"*!testInviteClientRetransmissionsWithRecovery!*");
   TestUser t102(Uri("sip:102@localhost"), "102", "102");
   TestUser t103(Uri("sip:103@localhost"), "103", "103");

     first sequence of test (register the test user)
     |

   Seq(t102.registerUser(60, t102.getDefaultContacts()),
       t102.expect(REGISTER/407, from(proxy),
                            WaitForResponse, t102.digestRespond()),
       t102.expect(REGISTER/200, from(proxy),
                            WaitForResponse, t102.noAction()),
       WaitForEndOfSeq);

     execute the above sequence
     |

   ExecuteSequences();

     second sequence of test
     |

   Seq(t103.invite(t102),

                  optional expectation -- event may or may not occur
                  |

       optional(t103.expect(INVITE/100, from(proxy),
                                     WaitFor100, t103.noAction())),
       t103.expect(INVITE/407, from(proxy),

                                               perform all argument actions in order
                                               |                   send ACK to complete INVITE transaction
                                               |                   |

                            WaitForResponse, chain(t103.ack(),
                                                   t103.digestRespond())),

         sets up two or more parallel Sub)sequences
         |   one of the Sub)sequences
         |   |   single expectation is optional; match before, during, or after other subsequence
         |   |   |

       And(Sub(optional(t103.expect(INVITE/100, from(proxy),
                                             WaitFor100, t103.noAction()))),

             other Sub)seqeuence
             |                                contact must be the sender (match predicate)
             |                                |

           Sub(t102.expect(INVITE, contact(t103),
                                    WaitForCommand, chain(t102.ring(),
                                                          t102.answer())),
               t103.expect(INVITE/180, from(t102),
                                    WaitFor100, t103.noAction()),
               t103.expect(INVITE/200, contact(t102),
                                    WaitForResponse, t103.ack()),
               t102.expect(ACK, from(acct1[ext103]),
                                    WaitForResponse, t103.noAction()))),
       WaitForEndOfTest);
   ExecuteSequences();
}

Time Flow[edit]

Retransmission Example[edit]

Description[edit]

This test exercises the SIP stack's handling of a retransmitted request. The situtation simulated is where the client failed to receive the first response so it sent the request again. Retransmitting covers loss of both request on the way out or the response on the way back.

  1. provision a TestUser
  2. sequence
    1. send a register message; store the message
    2. expect a digest challenge response (407), retransmit the original request
    3. expect a digest challenge response (407), send digest authed request
    4. expect a 200 OK to the authed REGISTER

Code[edit]

void testRegisterClientRetransmits()
{
   WarningLog(<<"*!testRegisterClientRetransmits!*");                                                                               

   TestUser t102(Uri("sip:102@localhost"), "102", "102");

     provide storage for message to retransmit during test
     |

   boost::shared_ptr<SipMessage> reg;                                                                                               

         remember a message
         |    store the message here
         |    |    remember the message produced by this action
         |    |    |

   Seq(save(reg, t102.registerUser(60, t102.getDefaultContacts())),
       t102.expect(REGISTER/407, from(proxy), WaitForResponse, t102.digestRespond()),
       t102.expect(REGISTER/200, from(proxy),

                                                             retransmit the remembered message
                                                             |

                                          WaitForResponse, t102.retransmit(reg)),
       t102.expect(REGISTER/200, from(proxy),
                                          WaitForResponse, t102.noAction()),
       WaitForEndOfTest);                                                                                                           
   ExecuteSequences();                                                                                                              
}

Time Flow[edit]

(italics represent system under test,
bold represents sequence execution,
plain type represents framework)

  1. provisioning happens immediately (normal C++ code)
  2. sequence arguments are evalulated; the return values are descriptions of the actions and expectations
  3. initial action is stored
  4. expectation stack is created and populated with expectations in order
  5. stray events timer value is stored
  6. ExecuteSequences
    1. start action is executed -- REGISTER is sent
    2. REGISTER gets to proxy and runs features etc -- proxy sends back a 407 response
    3. 407 response is delivered to the intepreter as the event
    4. available expectations are handed the event
      1. only first expectation is available
      2. first expectation's predicate is executed against the event
        1. is it a REGISTER response? yes!
        2. is it a 407? yes!
        3. is it from the proxy (check via)? yes!
        4. expectation matches!
      3. first expectation's action is executed
        1. executes TestSipEndPoint::Retransmit(event) -- retransmit the store message
    5. first expectation is popped from the stack and is no longer available; second expectation becomes available
    6. retransmitted REGISTER gets to the proxy's stack -- stack resends the response
    7. 407 response retransmission is delivered to the intepreter as the event
    8. available expectations are handed the event
    9. only second expectation is available
      1. second expectation's predicate is executed against the event
        1. is it a REGISTER response? yes!
        2. is it a 407? yes!
        3. is it from the proxy (check via)? yes!
        4. expectation matches!
      2. second expectation's action is executed
        1. executes TestUser::DigestRespond::go(event) -- send REGISTER with authentication
    10. REGISTER with auth gets to proxy -- proxy sends back a 200
    11. second expectation is popped from the stack and is no longer available; second expectation becomes available
    12. 200 resonse is delivered to the interpreter as the event
    13. available expectations are handed the event
      1. only third expectation is available
      2. third expectation's predicate is executed against the event
        1. is it a REGISTER response? yes!
        2. is it a 200? yes!
        3. is it from the proxy (check via)? yes!
        4. expectation matches!
        5. third expectation's actions are executed
          1. execute no-op -- does nothing
    14. third expectation is popped from the stack and is no longer available
    15. no further expectations -- start wait for stray events timer
    16. wait for stray events timer fires
    17. test ends successfully

Branching Example[edit]

Description[edit]

Code[edit]

void testInviteClientRetransmissionsWithRecovery()
{
   WarningLog(<<"*!testInviteClientRetransmissionsWithRecovery!*");
   TestUser t102(Uri("sip:102@localhost"), "102", "102");
   TestUser t103(Uri("sip:103@localhost"), "103", "103");

     first sequence of test (register the test user)
     |

   Seq(t102.registerUser(60, t102.getDefaultContacts()),
       t102.expect(REGISTER/407, from(proxy),
                            WaitForResponse, t102.digestRespond()),
       t102.expect(REGISTER/200, from(proxy),
                            WaitForResponse, t102.noAction()),
       WaitForEndOfSeq);

     execute the above sequence
     |

   ExecuteSequences();

     second sequence of test
     |

   Seq(t103.invite(ext102),

                  optional expectation -- event may or may not occur
                  |

       optional(t103.expect(INVITE/100, from(proxy),
                                     WaitFor100, t103.noAction())),
       t103.expect(INVITE/407, from(proxy),

                                               perform all argument actions in order
                                               |

                            WaitForResponse, chain(t103.ack(),
                                                   t103.digestRespond())),

         sets up two or more parallel Sub)sequences
         |   one of the Sub)sequences
         |   |   single expectation is optional; match before, during, or after other subsequence
         |   |   |

       And(Sub(optional(t103.expect(INVITE/100, from(proxy),
                                             WaitFor100, t103.noAction()))),

             other Sub)seqeuence
             |                                contact must be the sender (match predicate)
             |                                |

           Sub(t102.expect(INVITE, contact(t103),

                                                                    output argument to InfoLog
                                                                    |

                                    WaitForCommand, t103.note("R1")),
               t102.expect(INVITE, contact(t103),
                                    1000, t103.note("R2")),
               t102.expect(INVITE, contact(t103),
                                    2000, chain(t103.note("R3"),
                                                t102.ring(),
                                                t102.answer())),
               t103.expect(INVITE/180, from(t102),
                                    WaitFor100, t103.noAction()),
               t103.expect(INVITE/200, contact(t102),
                                    WaitForResponse, t103.ack()),
               t102.expect(ACK, from(t103),
                                    WaitForResponse, t103.noAction()))),
       WaitForEndOfTest);
   ExecuteSequences();
}

Time Flow[edit]

Primitives[edit]

TFM by itself does not provide concrete actions, expectations, or predicates. Each area of test must supply its own. Concrete types of each of these abtractions are accessed from a derived TestEndPoint type. The general pattern for a primitive is to create an accessor on a concrete endpoint that creates and returns an instance of the primitive. The primitive is implemented as a memento; the instance holds all of the state it needs to execute. Execution of the primitive is delayed -- the TFM interpreter invokes the function-like interface (either operator() or go method) when appropriate.

In addition, derived Event types are defined for each area of test. Events are critical building blocks for creating tests.

Additional primitives (for example, save) are top level object or functions declared within the scope of a test, rather than as an accessor from a concrete endpoint.

TFM test primitives and events are declared in sip/tfm/TestSipEndpoint.hxx.

Provisioning[edit]

To be filled in with examples of how to subclass TestUser

Sequencing[edit]

sip/tfm/TestSipEndPoint.hxx declares the concrete sequence primitives and events for TFM tests.

Events[edit]

SipEvent is an abstract Event that wraps a SIP message. All TestSipEndPoint events are SipEvents. SipEvent uses a reference-count pointer to the underlying SIP message for memory management. The SIP message is publically accessible from the SipEvent and is used in predicates and actions.

TestSipEndPoint wraps each incoming SIP message in a SipEvent in TestSipEndPoint::process. process does some dialog and message management and can trigger failures. Typically the SIP event is simply wrapped and passed to the SequenceSet for handling by the sequence interpreter.

Actions[edit]

CloseTransport[edit]

Accessed with the method closeTransport. This action is for side effect only. It is used to test error conditions around an unexpectedly closed transport.

ReInvite[edit]

(Note: shouldn't this be a MessageAction?) Accessed with the reInvite method on TestSipEndPoint. Creates an INVITE message within an existing INVITE dialog. Throws if there is no existing INVITE dialog -- the throw will cause the test to fail.

Subscribe[edit]

(Note: shouldn't this be a MessageAction?) Accessed with the subscribe method on TestSipEndPoint. Creates and sends a SUBSCRIBE message. There are several interfaces for specifying the request Url (the document of interest).

Refer[edit]

Accessed with the refer and referReplaces methods on TestSipEndPoint.

MessageActions[edit]

MessageActions are mementos for executable funtions that may be used to start a sequence or may be used in an expectation if the triggering event is not required. The message produced by the message action can be conditioned #Message Conditioners.

Invite[edit]

Accessed with the invite method on TestSipEndPoint. User to create and send an INVITE message. There a several interfaces for specifying the request Uri.

Request[edit]

Accessed with the info method on TestSipEndPoint. Used to create and send an INFO message.

RemoveAllRegistrationBindings[edit]

Accessed with removeRegistrationBindings method on TestUser. Userd to create and send a REGISTER message that unregisters all bindings.

Register[edit]

Accessed with the register method on TestSipEndPoint. Used to create and send a REGISTER message to make a registration binding.

Match Predicates[edit]

All TFM expectations derive from TestSipEndPpoint::SipExpect, which derives from TestEndPoint::ExpectBase. The relevant matching method is TestEndPoint::ExpectBase::isMatch, which is abstract.

SipExpect constructors takes two matching predicates:

Matcher[edit]

A derived Matcher predicate that takes a SipEvent

From[edit]

This predicate memento remembers the TestSipEndPoint it was created from and the element from which the event is expected. There are three forms of From corresponding to the type of element to expect:

  • TestSipEndPoint - from must be the endpoint
  • TestProxy - last via must be this element
  • an arbitrary Uri
    • request - must be a single contact and the Uri must match the contact
    • response - check the via of the orinator
Contact[edit]

This predicate memento remembers the TestSipEndPoint it was created from and the TestSipEndPoint that will correspond to the contact (there must be exactly one contact for the predicate to succeed).

Check Predicates[edit]

Check predicates are functors that implement bool operator()(boost::shared_ptr<Event> event) const. The method check takes a functor of the above form and creates an action memento from it. When the expect action is triggered, the predicate is executed given the event. If the predicate returns false, the test fails.

  • CheckRport - created with an expected rport. If the event rport (from the Via) does not match the given rport, this predicate returns false.

MessageExpectActions[edit]

MessageExpectActions execute arbitrary code when their expectation matches. MessageExpectActions must have a SipEvent available when they fire.

The message expect action hierachy is relatively deep. For clarity, the hierarchy is on its own page TFM Message Expect Actions.

Message Conditioners[edit]

Message conditioners are functors that modify or store the message. Message conditioners can be composed; each returns the message as conditioned for possible further conditioning. Message condition composition is performed by explcitly composing conditioning mementos. That is compose(a, b) produces a new conditioner that applies the condition a to the results of applying the condition b to the message returned from the action. If no message is returned by the action, no conditions are applied and no message is sent.

IdentityMessageConditioner[edit]

This conditioner does nothing to the message. It is the default message conditioner for all TFM_Message_Expect_Actions#MessageExpectActions.

SaveMessage[edit]

Keep a pointer to the message as it passes through.