--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/Makefile Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,44 @@
+TEXI2HTML = texi2html
+TEXI2PDF = texi2dvi --pdf
+EPSTOPDF = epstopdf
+DIA = dia
+CONVERT = convert
+CSS = --css-include=testing.css
+SPLIT = --split section
+
+FIGURES = figures
+VPATH = $(FIGURES)
+
+IMAGES_EPS = \
+
+IMAGES_PNG = ${IMAGES_EPS:.eps=.png}
+IMAGES_PDF = ${IMAGES_EPS:.eps=.pdf}
+
+IMAGES = $(IMAGES_EPS) $(IMAGES_PNG) $(IMAGES_PDF)
+
+CHAPTERS = \
+ testing.texi \
+ overview.texi \
+ propagation-loss.texi \
+
+%.eps : %.dia; $(DIA) -t eps $< -e $@
+%.png : %.dia; $(DIA) -t png $< -e $@
+%.pdf : %.eps; $(EPSTOPDF) $< -o=$@
+
+all: $(IMAGES) testing.pdf testing.html testing/testing.html
+
+testing.pdf: $(IMAGES) $(CHAPTERS)
+ $(TEXI2PDF) testing.texi
+
+testing.html: $(IMAGES) $(CHAPTERS)
+ $(TEXI2HTML) ${CSS} testing.texi
+
+testing/testing.html: $(IMAGES) $(CHAPTERS)
+ $(TEXI2HTML) ${CSS} ${SPLIT} testing.texi
+
+figures-clean:
+ rm -rf $(IMAGES)
+
+clean: figures-clean
+ rm -rf testing.aux testing.cp testing.cps testing.fn testing.ky testing.pg
+ rm -rf testing.tp testing.vr testing.toc testing.log testing.pdf testing.html testing/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/background.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,215 @@
+@c ========================================================================
+@c Background
+@c ========================================================================
+
+@node Background
+@chapter Background
+
+Writing defect-free software is a difficult proposition. There are many
+dimensions to the problem and there is much confusion regarding what is
+meant by different terms in different contexts. We have found it worthwhile
+to spend a little time reviewing the subject and defining some terms.
+
+Software testing may be loosely defined as the process of executing a program
+with the intent of finding errors. When one enters a discussion regarding
+software testing, it quickly becomes apparent that there are many distinct
+mind-sets with which one can approach the subject.
+
+For example, one could break the process into broad functional categories
+like ``correctness testing,'' ``performance testing,'' ``robustness testing''
+and ``security testing.'' Another way to look at the problem is by life-cycle:
+``requirements testing,'' ``design testing,'' ``acceptance testing,'' and
+``maintenance testing.'' Yet another view is by the scope of the tested system.
+In this case one may speak of ``unit testing,'' ``component testing,''
+``integration testing,'' and ``system testing.'' These terms are also not
+standardized in any way, and so ``maintenance testing'' and ``regression
+testing'' may be heard interchangeably. Additionally, these terms are often
+misused.
+
+There are also a number of different philosophical approaches to software
+testing. For example, some organizations advocate writing test programs
+before actually imlementing the desired software, yielding ``test-driven
+development.'' Some organizations advocate testing from a customer perspective
+as soon as possible, following a parallel with the agile development process:
+``test early and test often.'' This is sometimes called ``agile testing.'' It
+seems that there is at least one approach to testing for every development
+methodology.
+
+The @command{ns-3} project is not in the business of advocating for any one of
+these processes, but the project as a whole has requirements that help inform
+the test process.
+
+Like all major software products, @command{ns-3} has a number of qualities that
+must be present for the product to succeed. From a testing perspective, some
+of these qualities that must be addressed are that @command{ns-3} must be
+``correct,'' ``robust,'' ``performant'' and ``maintainable.'' Ideally there
+should be metrics for each of these dimensions that are checked by the tests
+to identify when the product fails to meed its expectations / requirements.
+
+@node Correctness
+@section Correctness
+
+The essential purpose of testing is to determine that a piece of software
+behaves ``correctly.'' For @command{ns-3} this means that if we simulate
+something, the simulation should faithfully represent some physical entity or
+process to a specified accuracy and precision.
+
+It turns out that there are two perspectives from which one can view
+correctness. Verifying that a particular process is implemented according
+to its specification is generically called verification. The process of
+deciding that the specification is correct is generically called validation.
+
+@node ValidationAndVerification
+@section Validation and Verification
+
+A computer model is a mathematical or logical representation of something. It
+can represent a vehicle, a frog or a networking card. Models can also represent
+processes such as global warming, freeway traffic flow or a specification of a
+networking protocol. Models can be completely faithful representations of a
+logical process specification, but they necessarily can never completely
+simulate a physical object or process. In most cases, a number of
+simplifications are made to the model to make simulation computationally
+tractable.
+
+Every model has a @emph{target system} that it is attempting to simulate. The
+first step in creating a simulation model is to identify this target system and
+the level of detail and accuracy that the simulation is desired to reproduce.
+In the case of a logical process, the target system may be identified as ``TCP
+as defined by RFC 793.'' In this case, it will probably be desirable to create
+a model that completely and faithfully reproduces RFC 793. In the case of a
+physical process this will not be possible. If, for example, you would like to
+simulate a wireless networking card, you may determine that you need, ``an
+accurate MAC-level implementation of the 802.11 specification and [...] a
+not-so-slow PHY-level model of the 802.11a specification.''
+
+Once this is done, one can develop an abstract model of the target system. This
+is typically an exercise in managing the tradeoffs between complexity, resource
+requiremens and accuracy. The process of developing an abstract model has been
+called @emph{model qualification} in the literature. In the case of a TCP
+protocol, this process results in a design for a collection of objects,
+interactions and behaviors that will fully implement RFC 793 in @command{ns-3}.
+In the case of the wireless card, this process results in a number of tradeoffs
+to allow the physical layer to be simulated and the design of a network device
+and channel for ns-3, along with the desired objects, interactions and behaviors.
+
+This abstract model is then developed into an @command{ns-3} model that
+implements the abstract model as a computer program. The process of getting the
+implementation to agree with the abstract model is called @emph{model
+verification} in the literature.
+
+The process so far is open loop. What remains is to make a determination that a
+given ns-3 model has some connection to some reality -- that a model is an
+accurate representation of a real system, whether a logical process or a physical
+entity.
+
+If one is going to use a simulation model to try and predict how some real
+system is going to behave, there must be some reason to believe your results --
+i.e., can one trust that an inference made from the model translates into a
+correct prediction for the real system. The process of getting the ns-3 model
+behavior to agree with the desired target system behavior as defined by the model
+qualification process is called @emph{model validation} in the literature. In the
+case of a TCP implementation, you may want to compare the behavior of your ns-3
+TCP model to some reference implementation in order to validate your model. In
+the case of a wireless physical layer simulation, you may want to compare the
+behavior of your model to that of real hardware in a controlled setting,
+
+The @command{ns-3} testing environment provides tools to allow for both model
+validation and testing, and encourages the publication of validation results.
+
+@node Robustness
+@section Robustness
+
+Robustness is the quality of being able to withstand stresses, or changes in
+environments, inputs or calculations, etc. A system or design is ``robust''
+if it can deal with such changes with minimal loss of functionality.
+
+This kind of testing is usually done with a particular focus. For example, the
+system as a whole can be run on many different system configurations to
+demonstrate that it can perform correctly in a large number of environments.
+
+The system can be also be stressed by operating close to or beyond capacity by
+generating or simulating resource exhaustion of various kinds. This genre of
+testing is called ``stress testing.''
+
+The system and its components may be exposed to so-called ``clean tests'' that
+demostrate a positive result -- that is that the system operates correctly in
+response to a large variation of expected configurations.
+
+The system and its components may also be exposed to ``dirty tests'' which
+provide inputs outside the expected range. For example, if a module expects a
+zero-terminated string representation of an integer, a dirty test might provide
+an unterminated string of random characters to verify that the system does not
+crash as a result of this unexpected input. Unfortunately, detecting such
+``dirty'' input and taking preventive measures to ensure the system does not
+fail catasrophically can require a huge amount of development overhead. In
+order to reduce development time, a decision was taken early on in the project
+to minimize the amount of parameter validation and error handling in the
+@command{ns-3} codebase. For this reason, we do not spend much time on dirty
+testing -- it would just uncover the results of the design decision we know
+we took.
+
+We do want to deonstrate that @command{ns-3} software does work across some set
+of conditions. We borrow a couple of definitions to narrow this down a bit.
+The @emph{domain of applicability} is a set of prescribed conditions for which
+the model has been tested, compared against reality to the extent possible, and
+judged suitable for use. The @emph{range of accuracy} is an agreement between
+the computerized model and reality within a domain of applicability.
+
+The @command{ns-3} testing environment provides tools to allow for setting up
+and running test environments over multiple systems (buildbot) and provides
+classes to encourage clean tests to verify the operation of the system over the
+expected ``domain of applicability'' and ``range of accuraccy.''
+
+@node Performant
+@section Performant
+
+Okay, ``performant'' isn't a real English word. It is, however, a very concise
+neologism that is quite often used to describe what we want @command{ns-3} to
+be: powerful and fast enough to get the job done.
+
+This is really about the broad subject of software performance testing. One of
+the key things that is done is to compare two systems to find which performs
+better (cf benchmarks). This is used to demonstrate that, for example,
+@code{ns-3} can perform a basic kind of simulation at least as fast as a
+competing product, or can be used to identify parts of the system that perform
+badly.
+
+In the @code{ns-3} test framework, we provide support for timing various kinds
+of tests.
+
+@node Maintainability
+@section Maintainability
+
+A software product must be maintainable. This is, again, a very broad
+statement, but a testing framework can help with the task. Once a model has
+been developed, validated and verified, we can repeatedly execute the suite
+of tests for the entire system to ensure that it remains valid and verified
+over its lifetime.
+
+When a feature stops functioning as intended after some kind of change to the
+system is integrated, it is called generically a regression. Originally the
+term regression referred to a change that caused a previously fixed bug to
+reappear, but the term has evolved to describe any kind of change that breaks
+existing functionality. There are many kinds of regressions that may occur
+in practice.
+
+A @emph{local regression} is one in which a change affects the changed component
+directy. For example, if a component is modified to allocate and free memory
+but stale pointers are used, the component itself fails.
+
+A @emph{remote regression} is one in which a change to one component breaks
+functionality in another component. This reflects violation of an implied but
+possibly unrecognized contract between components.
+
+An @emph{unmasked regression} is one that creates a situation where a previously
+existing bug that had no affect is suddenly exposed in the system. This may
+be as simple as exercising a code path for the first time.
+
+A @emph{performance regression} is one that causes the performance requirements
+of the system to be violated. For example, doing some work in a low level
+function that may be repeated large numbers of times may suddenly render the
+system unusable from certain perspectives.
+
+The @command{ns-3} testing framework provides tools for automating the process
+used to validate and verify the code in nightly test suites to help quickly
+identify possible regressions.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/how-to-write-tests.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,8 @@
+@c ========================================================================
+@c How to write tests
+@c ========================================================================
+
+@node How to write tests
+@chapter How to write tests
+
+To be completed.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/overview.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,16 @@
+@c ========================================================================
+@c Overview
+@c ========================================================================
+
+@node Overview
+@chapter Overview
+
+This document is concerned with the testing and validation of @command{ns-3}
+software.
+
+This document provides
+@itemize @bullet
+@item a description of the ns-3 testing framework;
+@item a guide to model developers or new model contributors for how to write tests;
+@item validation and verification results reported to date.
+@end itemize
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/propagation-loss.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,121 @@
+@node Propagation Loss Models
+@chapter Propagation Loss Models
+@anchor{chap:propagation-loss-models}
+
+This chapter describes validation of ns-3 propagation loss models.
+
+@section FriisPropagationLossModel
+
+@subsection Model reference
+
+From source: @uref{http://www.scribd.com/doc/6650712/Wireless-CommunicationsPrinciples-and-Practice-Theodore-S,, Wireless Communications-Principles and Practice ,Theodore S Rappaport pg. 71 }
+
+Given equation:
+@verbatim
+Pr = Pt*Gt*Gr*lmb^2/((4*pi)^2*d^2*L)
+
+Pt = 10^(17.0206/10)/10^3 = .05035702
+Pr = .05035702*.125^2/((4*pi)^2*d*1) = 4.98265e-6/d^2
+
+bandwidth = 2.2*10^7
+m_noiseFigure = 5.01187
+noiseFloor = ((Thermal noise (K)* BOLTZMANN * bandwidth)* m_noiseFigure)
+noiseFloor = ((290*1.3803*10^-23*2.2*10^7)*5.01187) = 4.41361e-13W
+no interference, so SNR = Pr/4.41361e-13W
+
+Distance :: Pr :: SNR
+100 4.98265e-10W 1128.93
+500 1.99306e-11W 45.1571
+1000 4.98265e-12W 11.2893
+2000 1.24566e-12W 2.82232
+3000 5.53628e-13W 1.25436
+4000 3.11416e-13W 0.70558
+5000 1.99306e-13W 0.451571
+6000 1.38407e-13W 0.313591
+@end verbatim
+
+@subsection Validation test
+
+Test program available online at: @uref{http://xxx.xxx.com,,}
+
+Taken at default settings (packetSize = 1000, numPackets = 1, lambda = 0.125, 802.11b at 2.4GHz):
+@verbatim
+Distance :: Pr :: SNR
+100 4.98265e-10W 1128.93
+500 1.99306e-11W 45.1571
+1000 4.98265e-12W 11.2893
+2000 1.24566e-12W 2.82232
+3000 5.53628e-13W 1.25436
+4000 3.11416e-13W 0.70558
+5000 1.99306e-13W 0.451571
+6000 1.38407e-13W 0.313591
+7000 1.01687e-13W 0.230393
+8000 7.78539e-14W 0.176395
+@end verbatim
+
+@subsection Discussion
+
+As can be seen, the SNR outputted from the simulator, and the SNR computed from the source's equation are identical.
+
+@section LogDistancePropagationLossModel
+
+@subsection Model reference
+
+From source: @uref{http://www.plextek.co.uk/papers/aps2005mcw.pdf,, Urban Propagation Measurements and Statistical Path Loss Model at 3.5 GHz, Marcus C. Walden, Frank J. Rowsell}
+
+Given equation:
+@verbatim
+PL{dBm} = PL(d0) + 10*n*log(d/d0) + Xs
+
+PL(1) from friis at 2.4GHz: 40.045997dBm
+PL{dBm} = 10*log(.050357/Pr) = 40.045997 + 10*n*log(d) + Xg
+Pr = .050357/(10^((40.045997 + 10*n*log(d) + Xg)/10))
+
+bandwidth = 2.2*10^7
+m_noiseFigure = 5.01187
+no interference, so SNR = Pr/4.41361e-13W
+@end verbatim
+
+taking Xg to be constant at 0 to match ns-3 output:
+@verbatim
+Distance :: Pr :: SNR
+10 4.98265e-9 11289.3
+20 6.22831e-10 1411.16
+40 7.78539e-11 176.407
+60 2.30678e-11 52.2652
+80 9.73173e-12 22.0494
+100 4.98265e-12 11.2893
+200 6.22831e-13 1.41116
+500 3.98612e-14 .090314
+1000 4.98265e-15 .011289
+@end verbatim
+
+@subsection Validation test
+
+Test program available online at: @uref{http://xxx.xxx.com,,}
+
+Taken at default settings (packetSize = 1000, numPackets = 1, exponent = 3, reference loss = 46.6777, 802.11b at 2.4GHz)
+@verbatim
+Distance :: Pr :: snr
+10 4.98471e-9 11293.9
+20 6.23089e-10 1411.74
+40 7.78861e-11 176.468
+60 2.30774e-11 52.2868
+80 9.72576e-12 22.0585
+100 4.98471e-12 11.2939
+200 6.23089e-13 1.41174
+500 3.98777e-14 0.0903516
+1000 4.98471e-15 0.0112939
+@end verbatim
+
+
+@subsection Discussion
+There is a ~.04% error between these results. I do not believe this is
+due to rounding, as the results taken from the equation from the source
+match exactly with the Friis results taken at one less power of ten.
+(Friis and LogDistance can be modeled by Pt*Gt*Gr*lmb^2/((4*pi)^2*d^n*L),
+where n is the exponent. n is 2 for Friis, and 3 for logDistance, which
+accounts for the power of ten. ie: Friis at 100m is equivalent to LogDistance
+at 10m.) Perhaps the ns-3 takes the random number into account despite
+not being listed in the source.
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/testing-framework.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,567 @@
+@c ========================================================================
+@c Testing framework
+@c ========================================================================
+
+@node TestingFramework
+@chapter Testing Framework
+
+@node BuildBots
+@section Buildbots
+
+The @command{ns-3} testing framework is composed of several major pieces. At
+the highest level are the buildbots (build robots). If you are unfamiliar with
+this system look at @uref{http://djmitche.github.com/buildbot/docs/0.7.11/}.
+This is an open-source automated system that allows @command{ns-3} to be rebuilt
+and tested each time something has changed. By running the buildbots on a number
+of different systems we can ensure that @command{ns-3} builds and executes
+properly on all of its supported systems.
+
+Users (and developers) typically will not interact with the buildbot system other
+than to read its messages regarding test results. If a failure is detected in
+one of the automated build and test jobs, the buildbot will send an email to the
+@emph{ns-developers} mailing list. This email will look something like:
+
+@verbatim
+ The Buildbot has detected a new failure of osx-ppc-g++-4.2 on NsNam.
+ Full details are available at:
+ http://ns-regression.ee.washington.edu:8010/builders/osx-ppc-g%2B%2B-4.2/builds/0
+
+ Buildbot URL: http://ns-regression.ee.washington.edu:8010/
+
+ Buildslave for this Build: darwin-ppc
+
+ Build Reason: The web-page 'force build' button was pressed by 'ww': ww
+
+ Build Source Stamp: HEAD
+ Blamelist:
+
+ BUILD FAILED: failed shell_5 shell_6 shell_7 shell_8 shell_9 shell_10 shell_11 shell_12 shell_13 shell_14 shell_15
+
+ sincerely,
+ -The Buildbot
+@end verbatim
+
+In the full details URL shown in the email, one can search for the keyword
+@code{failed} and select the @code{stdio} link for the corresponding step to see
+the reason for the failure.
+
+The buildbot will do its job quietly if there are no errors, and the system will
+undergo build and test cycles every day to verify that all is well.
+
+@node Testpy
+@section Test.py
+The buildbots use a Python program, @command{test.py}, that is reponsible for
+running all of the tests and collecting the resulting reports into a human-
+readable form. This program is also available for use by users and developers
+as well.
+
+@command{test.py} is very flexible in allowing the user to specify the number
+and kind of tests to run; and also the amount and kind of output to generate.
+
+By default, @command{test.py} will run all available tests and report status
+back in a very concise form. Running the command,
+
+@verbatim
+ ./test.py
+@end verbatim
+
+will result in a number of @code{PASS}, @code{FAIL}, @code{CRASH} or @code{SKIP}
+indications followed by the kind of test that was run and its display name.
+
+@verbatim
+ Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ 'build' finished successfully (0.939s)
+ FAIL: TestSuite ns3-wifi-propagation-loss-models
+ PASS: TestSuite object-name-service
+ PASS: TestSuite pcap-file-object
+ PASS: TestSuite ns3-tcp-cwnd
+ ...
+
+ PASS: TestSuite ns3-tcp-interoperability
+ PASS: Example csma-broadcast
+ PASS: Example csma-multicast
+@end verbatim
+
+This mode is indented to be used by users who are interested in determining if
+their distribution is working correctly, and by developers who are interested
+in determining if changes they have made have caused any regressions.
+
+If one specifies an optional output style, one can generate detailed descriptions
+of the tests and status. Available styles are @command{text} and @command{HTML}.
+The buildbots will select the HTML option to generate HTML test reports for the
+nightly builds using,
+
+@verbatim
+ ./test.py --html=nightly.html
+@end verbatim
+
+In this case, an HTML file named ``nightly.html'' would be created with a pretty
+summary of the testing done. A ``human readable'' format is available for users
+interested in the details.
+
+@verbatim
+ ./test.py --text=results.txt
+@end verbatim
+
+In the example above, the test suite checking the @command{ns-3} wireless
+device propagation loss models failed. By default no further information is
+provided.
+
+To further explore the failure, @command{test.py} allows a single test suite
+to be specified. Running the command,
+
+@verbatim
+ ./test.py --suite=ns3-wifi-propagation-loss-models
+@end verbatim
+
+results in that single test suite being run.
+
+@verbatim
+ FAIL: TestSuite ns3-wifi-propagation-loss-models
+@end verbatim
+
+To find detailed information regarding the failure, one must specify the kind
+of output desired. For example, most people will probably be interested in
+a text file:
+
+@verbatim
+ ./test.py --suite=ns3-wifi-propagation-loss-models --text=results.txt
+@end verbatim
+
+This will result in that single test suite being run with the test status written to
+the file ``results.txt''.
+
+You should find something similar to the following in that file:
+
+@verbatim
+FAIL: Test Suite ``ns3-wifi-propagation-loss-models'' (real 0.02 user 0.01 system 0.00)
+ PASS: Test Case "Check ... Friis ... model ..." (real 0.01 user 0.00 system 0.00)
+ FAIL: Test Case "Check ... Log Distance ... model" (real 0.01 user 0.01 system 0.00)
+ Details:
+ Message: Got unexpected SNR value
+ Condition: [long description of what actually failed]
+ Actual: 176.395
+ Limit: 176.407 +- 0.0005
+ File: ../src/test/ns3wifi/propagation-loss-models-test-suite.cc
+ Line: 360
+@end verbatim
+
+Notice that the Test Suite is composed of two Test Cases. The first test case
+checked the Friis propagation loss model and passed. The second test case
+failed checking the Log Distance propagation model. In this case, an SNR of
+176.395 was found, and the test expected a value of 176.407 correct to three
+decimal places. The file which implemented the failing test is listed as well
+as the line of code which triggered the failure.
+
+If you desire, you could just as easily have written an HTML file using the
+@code{--html} option as described above.
+
+Typically a user will run all tests at least once after downloading
+@command{ns-3} to ensure that his or her enviornment has been built correctly
+and is generating correct results according to the test suites. Developers
+will typically run the test suites before and after making a change to ensure
+that they have not introduced a regression with their changes. In this case,
+developers may not want to run all tests, but only a subset. For example,
+the developer might only want to run the unit tests periodically while making
+changes to a repository. In this case, @code{test.py} can be told to constrain
+the types of tests being run to a particular class of tests. The follwoing
+command will result in only the unit tests being run:
+
+@verbatim
+ ./test.py --constrain=unit
+@end verbatim
+
+Similarly, the following command will result in only the example smoke tests
+being run:
+
+@verbatim
+ ./test.py --constrain=unit
+@end verbatim
+
+To see a quick list of the legal kinds of constraints, you can ask for them
+to be listed. The following command
+
+@verbatim
+ ./test.py --kinds
+@end verbatim
+
+will result in the following list being displayed:
+
+@verbatim
+ Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ 'build' finished successfully (0.939s)Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ bvt: Build Verification Tests (to see if build completed successfully)
+ unit: Unit Tests (within modules to check basic functionality)
+ system: System Tests (spans modules to check integration of modules)
+ example: Examples (to see if example programs run successfully)
+ performance: Performance Tests (check to see if the system is as fast as expected)
+@end verbatim
+
+This list is displayed in increasing order of complexity of the tests. Any of these
+kinds of test can be provided as a constraint using the @code{--constraint} option.
+
+To see a quick list of all of the test suites available, you can ask for them
+to be listed. The following command,
+
+@verbatim
+ ./test.py --list
+@end verbatim
+
+will result in a list of the test suite being displayed, similar to :
+
+@verbatim
+ Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ 'build' finished successfully (0.939s)
+ ns3-wifi-propagation-loss-models
+ ns3-tcp-cwnd
+ ns3-tcp-interoperability
+ pcap-file-object
+ object-name-service
+ random-number-generators
+@end verbatim
+
+Any of these listed suites can be selected to be run by itself using the
+@code{--suite} option as shown above.
+
+Similarly to test suites, one can run a single example program using the @code{--example}
+option.
+
+@verbatim
+ ./test.py --example=udp-echo
+@end verbatim
+
+results in that single example being run.
+
+@verbatim
+ PASS: Example udp-echo
+@end verbatim
+
+Normally when example programs are executed, they write a large amount of trace
+file data. This is normally saved to the base directory of the distribution
+(e.g., /home/user/ns-3-dev). When @command{test.py} runs an example, it really
+is completely unconcerned with the trace files. It just wants to to determine
+if the example can be built and run without error. Since this is the case, the
+trace files are written into a @code{/tmp/unchecked-traces} directory. If you
+run the above example, you should be able to find the associated
+@code{udp-echo.tr} and @code{udp-echo-n-1.pcap} files there.
+
+The list of available examples is defined by the contents of the ``examples''
+directory in the distribution. If you select an example for execution using
+the @code{--example} option, @code{test.py} will not make any attempt to decide
+if the example has been configured or not, it will just try to run it and
+report the result of the attempt.
+
+When @command{test.py} runs, by default it will first ensure that the system has
+been completely built. This can be defeated by selecting the @code{--nowaf}
+option.
+
+@verbatim
+ ./test.py --list --nowaf
+@end verbatim
+
+will result in a list of the currently built test suites being displayed, similar to :
+
+@verbatim
+ ns3-wifi-propagation-loss-models
+ ns3-tcp-cwnd
+ ns3-tcp-interoperability
+ pcap-file-object
+ object-name-service
+ random-number-generators
+@end verbatim
+
+Note the absence of the @command{Waf} build messages.
+
+Finally, @code{test.py} provides a @command{--verbose} option which will print
+large amounts of information about its progress. It is not expected that this
+will be terribly useful for most users.
+
+@node TestTaxonomy
+@section Test Taxonomy
+
+As mentioned above, tests are grouped into a number of broadly defined
+classifications to allow users to selectively run tests to address the different
+kinds of testing that need to be done.
+
+@itemize @bullet
+@item Build Verification Tests
+@item Unit Tests
+@item System Tests
+@item Examples
+@item Performance Tests
+@end itemize
+
+@node BuildVerificationTests
+@subsection Build Verification Tests
+
+These are relatively simple tests that are built along with the distribution
+and are used to make sure that the build is pretty much working. Our
+current unit tests live in the source files of the code they test and are
+built into the ns-3 modules; and so fit the description of BVTs. BVTs live
+in the same source code that is built into the ns-3 code. Our current tests
+are examples of this kind of test.
+
+@node UnitTests
+@subsection Unit Tests
+
+Unit tests are more involved tests that go into detail to make sure that a
+piece of code works as advertized in isolation. There is really no reason
+for this kind of test to be built into an ns-3 module. It turns out, for
+example, that the unit tests for the object name service are about the same
+size as the object name service code itself. Unit tests are tests that
+check a single bit of functionality that are not built into the ns-3 code,
+but live in the same directory as the code it tests. It is possible that
+these tests check integration of multiple implementation files in a module
+as well. The file src/core/names-test-suite.cc is an example of this kind
+of test. The file src/common/pcap-file-test-suite.cc is another example
+that uses a known good pcap file as a test vector file. This file is stored
+locally in the src/common directory.
+
+@node SystemTests
+@subsection System Tests
+
+System tests are those that involve more than one module in the system. We
+have lots of this kind of test running in our current regression framework,
+but they are overloaded examples. We provide a new place for this kind of
+test in the directory ``src/tests''. The file
+src/test/ns3tcp/ns3-interop-test-suite.cc is an example of this kind of
+test. It uses NSC TCP to test the ns-3 TCP implementation. Often there
+will be test vectors required for this kind of test, and they are stored in
+the directory where the test lives. For example,
+ns3tcp-interop-response-vectors.pcap is a file consisting of a number of TCP
+headers that are used as the expected responses of the ns-3 TCP under test
+to a stimulus generated by the NSC TCP which is used as a ``known good''
+implementation.
+
+@node Examples
+@subsection Examples
+
+The examples are tested by the framework to make sure they built and will
+run. Nothing is checked, and currently the pcap files are just written off
+into /tmp to be discarded. If the examples run (don't crash) they pass this
+smoke test.
+
+@node PerformanceTests
+@subsection Performance Tests
+
+Performance tests are those which exercise a particular part of the system
+and determine if the tests have executed to completion in a reasonable time.
+
+@node RunningTests
+@section Running Tests
+
+Tests are typically run using the high level @code{test.py} program. They
+can also be run ``manually'' using a low level test-runner executable directly
+from @code{waf}.
+
+@node RunningTestsUnderTestRunnerExecutable
+@section Running Tests Under the Test Runner Executable
+
+The test-runner is the bridge from generic Python code to @command{ns-3} code.
+It is written in C++ and uses the automatic test discovery process in the
+@command{ns-3} code to find and allow execution of all of the various tests.
+
+Although it may not be used directly very often, it is good to understand how
+@code{test.py} actually runs the various tests.
+
+In order to execute the test-runner, you run it like any other ns-3 executable
+-- using @code{waf}. To get a list of available options, you can type:
+
+@verbatim
+ ./waf --run "test-runner --help"
+@end verbatim
+
+You should see something like the following:
+
+@verbatim
+ Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
+ 'build' finished successfully (0.353s)
+ --basedir=dir: Set the base directory (where to find src) to ``dir''
+ --constrain=test-type: Constrain checks to test suites of type ``test-type''
+ --help: Print this message
+ --kinds: List all of the available kinds of tests
+ --list: List all of the test suites (optionally constrained by test-type)
+ --out=file-name: Set the test status output file to ``file-name''
+ --suite=suite-name: Run the test suite named ``suite-name''
+ --verbose: Turn on messages in the run test suites
+@end verbatim
+
+There are a number of things available to you which will be familiar to you if
+you have looked at @command{test.py}. This should be expected since the test-
+runner is just an interface between @code{test.py} and @command{ns-3}. You
+may notice that example-related commands are missing here. That is because
+the examples are really not @command{ns-3} tests. @command{test.py} runs them
+as if they were to present a unified testing environment, but they are really
+completely different and not to be found here.
+
+One new option that appears here is the @code{--basedir} option. It turns out
+that the tests may need to reference the source directory of the @code{ns-3}
+distribution to find local data, so a base directory is always required to run
+a test. To run one of the tests directly from the test-runner, you will need
+to specify the test suite to run along with the base directory. So you could do,
+
+@verbatim
+ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object"
+@end verbatim
+
+Note the ``backward'' quotation marks on the @code{pwd} command. This will run
+the @code{pcap-file-object} test quietly. The only indication that
+you will get that the test passed is the @emph{absence} of a message from
+@code{waf} saying that the program returned something other than a zero
+exit code. To get some output from the test, you need to specify an output
+file to which the tests will write their XML status using the @code{--out}
+option. You need to be careful interpreting the results because the test
+suites will @emph{append} results onto this file. Try,
+
+@verbatim
+ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object --out=myfile.xml''
+@end verbatim
+
+If you look at the file @code{myfile.xml} you should see something like,
+
+@verbatim
+<TestSuite>
+ <SuiteName>pcap-file-object</SuiteName>
+ <TestCase>
+ <CaseName>Check to see that PcapFile::Open with mode ``w'' works</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <TestCase>
+ <CaseName>Check to see that PcapFile::Open with mode ``r'' works</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <TestCase>
+ <CaseName>Check to see that PcapFile::Open with mode ``a'' works</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <TestCase>
+ <CaseName>Check to see that PcapFileHeader is managed correctly</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <TestCase>
+ <CaseName>Check to see that PcapRecordHeader is managed correctly</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <TestCase>
+ <CaseName>Check to see that PcapFile can read out a known good pcap file</CaseName>
+ <CaseResult>PASS</CaseResult>
+ <CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
+ </TestCase>
+ <SuiteResult>PASS</SuiteResult>
+ <SuiteTime>real 0.00 user 0.00 system 0.00</SuiteTime>
+</TestSuite>
+@end verbatim
+
+If you are familiar with XML this should be fairly self-explanatory. It is
+also not a complete XML file since test suites are designed to have their
+output appended to a master XML status file as described in the @command{test.py}
+section.
+
+@node ClassTestRunner
+@section Class TestRunner
+
+The executables that run dedicated test programs use a TestRunner class. This
+class provides for automatic test registration and listing, as well as a way to
+exeute the individual tests. Individual test suites use C++ global constructors
+to add themselves to a collection of test suites managed by the test runner.
+The test runner is used to list all of the available tests and to select a test
+to be run. This is a quite simple class that provides three static methods to
+provide or Adding and Getting test suites to a collection of tests. See the
+doxygen for class @code{ns3::TestRunner} for details
+
+@node TestSuite
+@section Test Suite
+
+All @command{ns-3} tests are classified into Test Suites and Test Cases. A
+test suite is a collection of test cases that completely exercise a given kind
+of functionality. As described above, test suites can be classified as,
+
+@itemize @bullet
+@item Build Verification Tests
+@item Unit Tests
+@item System Tests
+@item Examples
+@item Performance Tests
+@end itemize
+
+This classification is exported from the TestSuite class. This class is quite
+simple, existing only as a place to export this type and to accumulate test
+cases. From a user perspective, in order to create a new TestSuite in the
+system one only has to define a new class that inherits from class @code{TestSuite}
+and perform these two duties.
+
+The following code will define a new class that can be run by @code{test.py}
+as a ``unit'' test with the display name, ``my-test-suite-name''.
+
+@verbatim
+ class MySuite : public TestSuite
+ {
+ public:
+ MyTestSuite ();
+ };
+
+ MyTestSuite::MyTestSuite ()
+ : TestSuite ("my-test-suite-name", UNIT)
+ {
+ AddTestCase (new MyTestCase);
+ }
+
+ MyTestSuite myTestSuite;
+@end verbatim
+
+The base class takes care of all of the registration and reporting required to
+be a good citizen in the test framework.
+
+@node TestCase
+@section Test Case
+
+Individual tests are created using a TestCase class. Common models for the use
+of a test case include "one test case per feature", and "one test case per method."
+Mixtures of these models may be used.
+
+In order to create a new test case in the system, all one has to do is to inherit
+from the @code{TestCase} base class, override the constructor to give the test
+case a name and override the @code{DoRun} method to run the test.
+
+@verbatim
+class MyTestCase : public TestCase
+{
+ MyTestCase ();
+ virtual bool DoRun (void);
+};
+
+MyTestCase::MyTestCase ()
+ : TestCase ("Check some bit of functionality")
+{
+}
+
+bool
+MyTestCase::DoRun (void)
+{
+ NS_TEST_ASSERT_MSG_EQ (true, true, "Some failure message");
+ return GetErrorStatus ();
+}
+@end verbatim
+
+@node Utilities
+@section Utilities
+
+There are a number of utilities of various kinds that are also part of the
+testing framework. Examples include a generalized pcap file useful for
+storing test vectors; a generic container useful for transient storage of
+test vectors during test execution; and tools for generating presentations
+based on validation and verification testing results.
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/testing.css Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,156 @@
+body {
+ font-family: "Trebuchet MS", "Bitstream Vera Sans", verdana, lucida, arial, helvetica, sans-serif;
+ background: white;
+ color: black;
+ font-size: 11pt;
+}
+
+h1, h2, h3, h4, h5, h6 {
+# color: #990000;
+ color: #009999;
+}
+
+pre {
+ font-size: 10pt;
+ background: #e0e0e0;
+ color: black;
+}
+
+a:link, a:visited {
+ font-weight: normal;
+ text-decoration: none;
+ color: #0047b9;
+}
+
+a:hover {
+ font-weight: normal;
+ text-decoration: underline;
+ color: #0047b9;
+}
+
+img {
+ border: 0px;
+}
+
+#main th {
+ font-size: 12pt;
+ background: #b0b0b0;
+}
+
+.odd {
+ font-size: 12pt;
+ background: white;
+}
+
+.even {
+ font-size: 12pt;
+ background: #e0e0e0;
+}
+
+.answer {
+ font-size: large;
+ font-weight: bold;
+}
+
+.answer p {
+ font-size: 12pt;
+ font-weight: normal;
+}
+
+.answer ul {
+ font-size: 12pt;
+ font-weight: normal;
+}
+
+#container {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0px;
+}
+
+#feedback {
+ color: #b0b0b0;
+ font-size: 9pt;
+ font-style: italic;
+}
+
+#header {
+ position: absolute;
+ margin: 0px;
+ top: 10px;
+ height:96px;
+ left: 175px;
+ right: 10em;
+ bottom: auto;
+ background: white;
+ clear: both;
+}
+
+#middle {
+ position: absolute;
+ left: 0;
+ height: auto;
+ width: 100%;
+}
+
+#main {
+ position: absolute;
+ top: 50px;
+ left: 175px;
+ right: 100px;
+ background: white;
+ padding: 0em 0em 0em 0em;
+}
+
+#navbar {
+ position: absolute;
+ top: 75px;
+ left: 0em;
+ width: 146px;
+ padding: 0px;
+ margin: 0px;
+ font-size: 10pt;
+}
+
+#navbar a:link, #navbar a:visited {
+ font-weight: normal;
+ text-decoration: none;
+ color: #0047b9;
+}
+
+#navbar a:hover {
+ font-weight: normal;
+ text-decoration: underline;
+ color: #0047b9;
+}
+
+#navbar dl {
+ width: 146px;
+ padding: 0;
+ margin: 0 0 10px 0px;
+ background: #99ffff url(images/box_bottom2.gif) no-repeat bottom left;
+}
+
+#navbar dt {
+ padding: 6px 10px;
+ font-size: 100%;
+ font-weight: bold;
+ background: #009999;
+ margin: 0px;
+ border-bottom: 1px solid #fff;
+ color: white;
+ background: #009999 url(images/box_top2.gif) no-repeat top left;
+}
+
+#navbar dd {
+ font-size: 100%;
+ margin: 0 0 0 0px;
+ padding: 6px 10px;
+ color: #0047b9;
+}
+
+dd#selected {
+ background: #99ffff url(images/arrow.gif) no-repeat;
+ background-position: 4px 10px;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/testing/testing.texi Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,98 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename ns-3.info
+@settitle ns-3 manual
+@c @setchapternewpage odd
+@c %**end of header
+
+@ifinfo
+Documentation for the @command{ns-3} project is available in
+several documents and the wiki:
+@itemize @bullet
+@item @uref{http://www.nsnam.org/doxygen/index.html,,ns-3 Doxygen/Manual}: Documentation of the public APIs of the simulator
+@item @uref{http://www.nsnam.org/tutorial/index.html,,ns-3 Tutorial}
+@item @uref{http://www.nsnam.org/doc//index.html,,ns-3 Tutorial}
+@item Reference Manual
+@item @uref{http://www.nsnam.org/wiki/index.php,, ns-3 wiki}
+@end itemize
+
+This document is written in GNU Texinfo and is to be maintained in
+revision control on the @command{ns-3} code server. Both PDF and HTML versions
+should be available on the server. Changes to
+the document should be discussed on the ns-developers@@isi.edu mailing list.
+@end ifinfo
+
+@copying
+
+This is an @command{ns-3} reference manual.
+Primary documentation for the @command{ns-3} project is available in
+four forms:
+@itemize @bullet
+@item @uref{http://www.nsnam.org/doxygen/index.html,,ns-3 Doxygen}: Documentation of the public APIs of the simulator
+@item @uref{http://www.nsnam.org/docs/tutorial/index.html,,ns-3 Tutorial}
+@item @uref{http://www.nsnam.org/docs/manual/index.html,,ns-3 Manual}
+@item Testing and Validation (this document)
+@item @uref{http://www.nsnam.org/wiki/index.php,, ns-3 wiki}
+@end itemize
+
+This document is written in GNU Texinfo and is to be maintained in
+revision control on the @command{ns-3} code server. Both PDF and HTML
+versions should be available on the server. Changes to
+the document should be discussed on the ns-developers@@isi.edu mailing list.
+
+This software is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This software is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see @uref{http://www.gnu.org/licenses/}.
+@end copying
+
+@titlepage
+@title ns-3 Testing and Validation
+@author ns-3 project
+@author feedback: ns-developers@@isi.edu
+@today{}
+
+@c @page
+@vskip 0pt plus 1filll
+@insertcopying
+@end titlepage
+
+@c So the toc is printed at the start.
+@anchor{Full Table of Contents}
+@contents
+
+@ifnottex
+@node Top, Overview, Full Table of Contents
+@top ns-3 Manual (html version)
+
+For a pdf version of this document,
+see @uref{http://www.nsnam.org/docs/testing.pdf}.
+
+@insertcopying
+@end ifnottex
+
+@menu
+* Overview::
+* Background::
+* Testing framework::
+* How to write tests::
+* Propagation Loss Models::
+@end menu
+
+@include overview.texi
+@include background.texi
+@include testing-framework.texi
+@include how-to-write-tests.texi
+@include propagation-loss.texi
+
+@printindex cp
+
+@bye
Binary file src/common/known.pcap has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common/pcap-file-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,965 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sstream>
+
+#include "ns3/test.h"
+#include "ns3/pcap-file.h"
+
+using namespace ns3;
+
+// ===========================================================================
+// Some utility functions for the tests.
+// ===========================================================================
+
+uint16_t
+Swap (uint16_t val)
+{
+ return ((val >> 8) & 0x00ff) | ((val << 8) & 0xff00);
+}
+
+uint32_t
+Swap (uint32_t val)
+{
+ return ((val >> 24) & 0x000000ff) | ((val >> 8) & 0x0000ff00) | ((val << 8) & 0x00ff0000) | ((val << 24) & 0xff000000);
+}
+
+bool
+CheckFileExists (std::string filename)
+{
+ FILE * p = fopen (filename.c_str (), "rb");
+ if (p == 0)
+ {
+ return false;
+ }
+
+ fclose (p);
+ return true;
+}
+
+
+bool
+CheckFileLength (std::string filename, uint64_t sizeExpected)
+{
+ FILE * p = fopen (filename.c_str (), "rb");
+ if (p == 0)
+ {
+ return false;
+ }
+
+ fseek (p, 0, SEEK_END);
+
+ uint64_t sizeActual = ftell (p);
+ fclose (p);
+
+ return sizeActual == sizeExpected;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can do its most basic job
+// and create an empty pcap file.
+// ===========================================================================
+class WriteModeCreateTestCase : public TestCase
+{
+public:
+ WriteModeCreateTestCase ();
+ virtual ~WriteModeCreateTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+WriteModeCreateTestCase::WriteModeCreateTestCase ()
+ : TestCase ("Check to see that PcapFile::Open with mode \"w\" works")
+{
+}
+
+WriteModeCreateTestCase::~WriteModeCreateTestCase ()
+{
+}
+
+void
+WriteModeCreateTestCase::DoSetup (void)
+{
+ std::stringstream filename;
+ uint32_t n = rand ();
+ filename << n;
+ m_testFilename = "/tmp/" + filename.str () + ".pcap";
+}
+
+void
+WriteModeCreateTestCase::DoTeardown (void)
+{
+ remove (m_testFilename.c_str ());
+}
+
+bool
+WriteModeCreateTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ // Opening a new file in write mode should result in an empty file of the
+ // given name.
+ //
+ bool err = f.Open (m_testFilename, "w");
+
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+ f.Close ();
+
+ NS_TEST_ASSERT_MSG_EQ (CheckFileExists (m_testFilename), true,
+ "Open (" << m_testFilename << ", \"w\") does not create file");
+ NS_TEST_ASSERT_MSG_EQ (CheckFileLength (m_testFilename, 0), true,
+ "Open (" << m_testFilename << ", \"w\") does not result in an empty file");
+
+ //
+ // Calling Init() on a file created with "w" should result in a file just
+ // long enough to contain the pcap file header.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ err = f.Init (1234, 5678, 7);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (1234, 5678, 7) returns error");
+
+ f.Close ();
+
+ NS_TEST_ASSERT_MSG_EQ (CheckFileLength (m_testFilename, 24), true,
+ "Init () does not result in a file with a pcap file header");
+
+ //
+ // Opening an existing file in write mode should result in that file being
+ // emptied.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ f.Close ();
+
+ NS_TEST_ASSERT_MSG_EQ (CheckFileLength (m_testFilename, 0), true,
+ "Open (" << m_testFilename << ", \"w\") does not result in an empty file");
+
+ //
+ // Initialize the file again.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false,
+ "Open (" << m_testFilename << ", \"w\") returns error");
+
+ err = f.Init (1234, 5678, 7);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (1234, 5678, 7) returns error");
+
+ //
+ // Now we should be able to write to it since it was opened in "w" mode.
+ // This is just a permissions check so we don't actually look at the
+ // data.
+ //
+ uint8_t buffer[128];
+ err = f.Write (0, 0, buffer, 128);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Write (write-only-file " << m_testFilename << ") returns error");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can open an existing pcap
+// file.
+// ===========================================================================
+class ReadModeCreateTestCase : public TestCase
+{
+public:
+ ReadModeCreateTestCase ();
+ virtual ~ReadModeCreateTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+ReadModeCreateTestCase::ReadModeCreateTestCase ()
+ : TestCase ("Check to see that PcapFile::Open with mode \"r\" works")
+{
+}
+
+ReadModeCreateTestCase::~ReadModeCreateTestCase ()
+{
+}
+
+void
+ReadModeCreateTestCase::DoSetup (void)
+{
+ std::stringstream filename;
+ uint32_t n = rand ();
+ filename << n;
+ m_testFilename = "/tmp/" + filename.str () + ".pcap";
+}
+
+void
+ReadModeCreateTestCase::DoTeardown (void)
+{
+ remove (m_testFilename.c_str ());
+}
+
+bool
+ReadModeCreateTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ // Opening a non-existing file in read mode should result in an error.
+ //
+ bool err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Open (non-existing-filename " << m_testFilename << ", \"r\") does not return error");
+
+ NS_TEST_ASSERT_MSG_EQ (CheckFileExists (m_testFilename), false,
+ "Open (" << m_testFilename << ", \"r\") unexpectedly created a file");
+
+ //
+ // Okay, now create an uninitialized file using previously tested operations
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (filename, \"w\") returns error");
+ f.Close ();
+
+ //
+ // Opening this file should result in an error since it has no pcap file header.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Open (non-initialized-filename " << m_testFilename << ", \"r\") does not return error");
+
+ //
+ // Okay, now open that non-initialized file in write mode and initialize it
+ // Note that we open it in write mode to initialize it.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ err = f.Init (1234, 5678, 7);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (1234, 5678, 7) returns error");
+ f.Close ();
+
+ //
+ // Opening this file should now work since it has a pcap file header.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (initialized-filename " << m_testFilename << ", \"r\") returns error");
+
+ //
+ // Now we should not be able to write to it since it was opened in "r" mode
+ // even if it has been initialized..
+ //
+ uint8_t buffer[128];
+ err = f.Write (0, 0, buffer, 128);
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Write (read-only-file " << m_testFilename << ") does not return error");
+
+ f.Close ();
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can open an existing pcap
+// file for appending.
+// ===========================================================================
+class AppendModeCreateTestCase : public TestCase
+{
+public:
+ AppendModeCreateTestCase ();
+ virtual ~AppendModeCreateTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+AppendModeCreateTestCase::AppendModeCreateTestCase ()
+ : TestCase ("Check to see that PcapFile::Open with mode \"a\" works")
+{
+}
+
+AppendModeCreateTestCase::~AppendModeCreateTestCase ()
+{
+}
+
+void
+AppendModeCreateTestCase::DoSetup (void)
+{
+ std::stringstream filename;
+ uint32_t n = rand ();
+ filename << n;
+ m_testFilename = "/tmp/" + filename.str () + ".pcap";
+}
+
+void
+AppendModeCreateTestCase::DoTeardown (void)
+{
+ remove (m_testFilename.c_str ());
+}
+
+bool
+AppendModeCreateTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ // Opening a non-existing file in append mode should result in an error.
+ //
+ bool err = f.Open (m_testFilename, "a");
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Open (non-existing-filename " << m_testFilename << ", \"a\") does not return error");
+ f.Close ();
+
+ NS_TEST_ASSERT_MSG_EQ (CheckFileExists (m_testFilename), false,
+ "Open (" << m_testFilename << ", \"a\") unexpectedly created a file");
+
+ //
+ // Okay, now create an uninitialized file using previously tested operations
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+ f.Close ();
+
+ //
+ // Opening this file should result in an error since it has no pcap file header.
+ //
+ err = f.Open (m_testFilename, "a");
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Open (non-initialized-filename " << m_testFilename << ", \"a\") does not return error");
+
+ //
+ // Okay, now open that non-initialized file in write mode and initialize it.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (non-initialized-filename " << m_testFilename << ", \"w\") returns error");
+
+ err = f.Init (1234, 5678, 7);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (1234, 5678, 7) returns error");
+ f.Close ();
+
+ //
+ // Opening this file should now work since it has a pcap file header.
+ //
+ err = f.Open (m_testFilename, "a");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (initialized-filename " << m_testFilename << ", \"r\") returns error");
+
+ //
+ // We should be able to write to it since it was opened in "a" mode.
+ //
+ uint8_t buffer[128];
+ err = f.Write (0, 0, buffer, 128);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Write (append-mode-file " << m_testFilename << ") returns error");
+
+ f.Close ();
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can write out correct pcap
+// file headers in both endian cases, and then read them in correctly.
+// ===========================================================================
+class FileHeaderTestCase : public TestCase
+{
+public:
+ FileHeaderTestCase ();
+ virtual ~FileHeaderTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+FileHeaderTestCase::FileHeaderTestCase ()
+ : TestCase ("Check to see that PcapFileHeader is managed correctly")
+{
+}
+
+FileHeaderTestCase::~FileHeaderTestCase ()
+{
+}
+
+void
+FileHeaderTestCase::DoSetup (void)
+{
+ std::stringstream filename;
+ uint32_t n = rand ();
+ filename << n;
+ m_testFilename = "/tmp/" + filename.str () + ".pcap";
+}
+
+void
+FileHeaderTestCase::DoTeardown (void)
+{
+ remove (m_testFilename.c_str ());
+}
+
+bool
+FileHeaderTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ // Create an uninitialized file using previously tested operations
+ //
+ bool err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ //
+ // Initialize the pcap file header.
+ //
+ err = f.Init (1234, 5678, 7);
+ NS_TEST_ASSERT_MSG_EQ (err, false,
+ "Init (1234, 5678, 7) returns error");
+ f.Close ();
+
+ //
+ // Take a look and see what was done to the file
+ //
+ FILE *p = fopen (m_testFilename.c_str (), "r+b");
+ NS_TEST_ASSERT_MSG_NE (p, 0, "fopen(" << m_testFilename << ") should have been able to open a correctly created pcap file");
+
+ uint32_t val32;
+ uint16_t val16;
+
+ size_t result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() magic number");
+ NS_TEST_ASSERT_MSG_EQ (val32, 0xa1b2c3d4, "Magic number written incorrectly");
+
+ result = fread (&val16, sizeof(val16), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() version major");
+ NS_TEST_ASSERT_MSG_EQ (val16, 2, "Version major written incorrectly");
+
+ result = fread (&val16, sizeof(val16), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() version minor");
+ NS_TEST_ASSERT_MSG_EQ (val16, 4, "Version minor written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() time zone correction");
+ NS_TEST_ASSERT_MSG_EQ (val32, 7, "Version minor written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() sig figs");
+ NS_TEST_ASSERT_MSG_EQ (val32, 0, "Sig figs written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() snap length");
+ NS_TEST_ASSERT_MSG_EQ (val32, 5678, "Snap length written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() data link type");
+ NS_TEST_ASSERT_MSG_EQ (val32, 1234, "Data length type written incorrectly");
+
+ fclose (p);
+ p = 0;
+
+ //
+ // We wrote a native-endian file out correctly, now let's see if we can read
+ // it back in correctly.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (existing-initialized-file " << m_testFilename << ", \"r\") returns error");
+
+ NS_TEST_ASSERT_MSG_EQ (f.GetMagic (), 0xa1b2c3d4, "Read back magic number incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetVersionMajor (), 2, "Read back version major incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetVersionMinor (), 4, "Read back version minor incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetTimeZoneOffset (), 7, "Read back time zone offset incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetSigFigs (), 0, "Read back sig figs incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetSnapLen (), 5678, "Read back snap len incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetDataLinkType (), 1234, "Read back data link type incorrectly");
+
+ //
+ // Re-open the file to erase its contents.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ //
+ // Initialize the pcap file header, turning on swap mode manually to force
+ // the pcap file header to be written out in foreign-endian form, whichever
+ // endian-ness that might be.
+ //
+ err = f.Init (1234, 5678, 7, true);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (1234, 5678, 7) returns error");
+ f.Close ();
+
+ //
+ // Take a look and see what was done to the file. Everything should now
+ // appear byte-swapped.
+ //
+ p = fopen (m_testFilename.c_str (), "r+b");
+ NS_TEST_ASSERT_MSG_NE (p, 0, "fopen(" << m_testFilename << ") should have been able to open a correctly created pcap file");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() magic number");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap(uint32_t (0xa1b2c3d4)), "Magic number written incorrectly");
+
+ result = fread (&val16, sizeof(val16), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() version major");
+ NS_TEST_ASSERT_MSG_EQ (val16, Swap(uint16_t (2)), "Version major written incorrectly");
+
+ result = fread (&val16, sizeof(val16), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() version minor");
+ NS_TEST_ASSERT_MSG_EQ (val16, Swap(uint16_t (4)), "Version minor written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() time zone correction");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap(uint32_t (7)), "Version minor written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() sig figs");
+ NS_TEST_ASSERT_MSG_EQ (val32, 0, "Sig figs written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() snap length");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap(uint32_t (5678)), "Snap length written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() data link type");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap(uint32_t (1234)), "Data length type written incorrectly");
+
+ fclose (p);
+ p = 0;
+
+ //
+ // We wrote an opposite-endian file out correctly, now let's see if we can read
+ // it back in correctly.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (existing-initialized-file " << m_testFilename << ", \"r\") returns error");
+
+ NS_TEST_ASSERT_MSG_EQ (f.GetSwapMode (), true, "Byte-swapped file not correctly indicated");
+
+ NS_TEST_ASSERT_MSG_EQ (f.GetMagic (), 0xa1b2c3d4, "Read back magic number incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetVersionMajor (), 2, "Read back version major incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetVersionMinor (), 4, "Read back version minor incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetTimeZoneOffset (), 7, "Read back time zone offset incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetSigFigs (), 0, "Read back sig figs incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetSnapLen (), 5678, "Read back snap len incorrectly");
+ NS_TEST_ASSERT_MSG_EQ (f.GetDataLinkType (), 1234, "Read back data link type incorrectly");
+
+ f.Close ();
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can write pcap packet
+// records in both endian cases, and then read them in correctly.
+// ===========================================================================
+class RecordHeaderTestCase : public TestCase
+{
+public:
+ RecordHeaderTestCase ();
+ virtual ~RecordHeaderTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+RecordHeaderTestCase::RecordHeaderTestCase ()
+ : TestCase ("Check to see that PcapRecordHeader is managed correctly")
+{
+}
+
+RecordHeaderTestCase::~RecordHeaderTestCase ()
+{
+}
+
+void
+RecordHeaderTestCase::DoSetup (void)
+{
+ std::stringstream filename;
+ uint32_t n = rand ();
+ filename << n;
+ m_testFilename = "/tmp/" + filename.str () + ".pcap";
+}
+
+void
+RecordHeaderTestCase::DoTeardown (void)
+{
+ remove (m_testFilename.c_str ());
+}
+
+bool
+RecordHeaderTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ // Create an uninitialized file using previously tested operations
+ //
+ bool err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ //
+ // Initialize the pcap file header.
+ //
+ err = f.Init (37, 43, -7);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (37, 43, -7) returns error");
+
+ //
+ // Initialize a buffer with a counting pattern to check the data later.
+ //
+ uint8_t bufferOut[128];
+ for (uint32_t i = 0; i < 128; ++i)
+ {
+ bufferOut[i] = i;
+ }
+
+ //
+ // Now we should be able to write a packet to it since it was opened in "w"
+ // mode. The packet data written should be limited to 43 bytes in length
+ // by the Init() call above.
+ //
+ err = f.Write (1234, 5678, bufferOut, 128);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Write (write-only-file " << m_testFilename << ") returns error");
+ f.Close ();
+
+ //
+ // Let's peek into the file and see what actually went out for that
+ // packet.
+ //
+ FILE *p = fopen (m_testFilename.c_str (), "r+b");
+ NS_TEST_ASSERT_MSG_NE (p, 0, "fopen() should have been able to open a correctly created pcap file");
+
+ //
+ // A pcap file header takes up 24 bytes, a pcap record header takes up 16 bytes
+ // and we wrote in 43 bytes, so the file must be 83 bytes long. Let's just
+ // double check that this is exactly what happened.
+ //
+ fseek (p, 0, SEEK_END);
+ uint64_t size = ftell (p);
+ NS_TEST_ASSERT_MSG_EQ (size, 83, "Pcap file with one 43 byte packet is incorrect size");
+
+ //
+ // A pcap file header takes up 24 bytes, so we should see a pcap record header
+ // starting there in the file. We've tested this all before so we just assume
+ // it's all right and just seek to just past that point..
+ //
+ fseek (p, 24, SEEK_SET);
+
+ uint32_t val32;
+
+ size_t result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() seconds timestamp");
+ NS_TEST_ASSERT_MSG_EQ (val32, 1234, "Seconds timestamp written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() microseconds timestamp");
+ NS_TEST_ASSERT_MSG_EQ (val32, 5678, "Microseconds timestamp written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() included length");
+ NS_TEST_ASSERT_MSG_EQ (val32, 43, "Included length written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() actual length");
+ NS_TEST_ASSERT_MSG_EQ (val32, 128, "Actual length written incorrectly");
+
+ //
+ // Take a look and see what went out into the file. The packet data
+ // should be unchanged (unswapped).
+ //
+ uint8_t bufferIn[128];
+
+ result = fread (bufferIn, 1, 43, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 43, "Unable to fread() packet data of expected length");
+
+ for (uint32_t i = 0; i < 43; ++i)
+ {
+ NS_TEST_ASSERT_MSG_EQ (bufferIn[i], bufferOut[i], "Incorrect packet data written");
+ }
+
+ fclose (p);
+ p = 0;
+
+ //
+ // Let's see if the PcapFile object can figure out how to do the same thing
+ // correctly read in a packet.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"r\") of existing good file returns error");
+
+ uint32_t tsSec, tsUsec, inclLen, origLen, readLen;
+
+ err = f.Read (bufferIn, sizeof(bufferIn), tsSec, tsUsec, inclLen, origLen, readLen);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Read() of known good packet returns error");
+ NS_TEST_ASSERT_MSG_EQ (tsSec, 1234, "Incorrectly read seconds timestap from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (tsUsec, 5678, "Incorrectly read microseconds timestap from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (inclLen, 43, "Incorrectly read included length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (origLen, 128, "Incorrectly read original length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (readLen, 43, "Incorrectly constructed actual read length from known good packet given buffer size");
+
+ //
+ // Did the data come back correctly?
+ //
+ for (uint32_t i = 0; i < 43; ++i)
+ {
+ NS_TEST_ASSERT_MSG_EQ (bufferIn[i], bufferOut[i], "Incorrect packet data read from known good packet");
+ }
+
+ //
+ // We have to check to make sure that the pcap record header is swapped
+ // correctly. Open the file in write mode to clear the data.
+ //
+ err = f.Open (m_testFilename, "w");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"w\") returns error");
+
+ //
+ // Initialize the pcap file header, forcing the object into swap mode.
+ //
+ err = f.Init (37, 43, -7, true);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Init (37, 43, -7) returns error");
+
+ //
+ // Now we should be able to write a packet to it since it was opened in "w"
+ // mode. The packet data written should be limited to 43 bytes in length
+ // by the Init() call above.
+ //
+ err = f.Write (1234, 5678, bufferOut, 128);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Write (write-only-file " << m_testFilename << ") returns error");
+ f.Close ();
+
+ //
+ // Let's peek into the file and see what actually went out for that
+ // packet.
+ //
+ p = fopen (m_testFilename.c_str (), "r+b");
+ NS_TEST_ASSERT_MSG_NE (p, 0, "fopen() should have been able to open a correctly created pcap file");
+
+ //
+ // A pcap file header takes up 24 bytes, a pcap record header takes up 16 bytes
+ // and we wrote in 43 bytes, so the file must be 83 bytes long. Let's just
+ // double check that this is exactly what happened.
+ //
+ fseek (p, 0, SEEK_END);
+ size = ftell (p);
+ NS_TEST_ASSERT_MSG_EQ (size, 83, "Pcap file with one 43 byte packet is incorrect size");
+
+ //
+ // A pcap file header takes up 24 bytes, so we should see a pcap record header
+ // starting there in the file. We've tested this all before so we just assume
+ // it's all right and just seek past it.
+ //
+ fseek (p, 24, SEEK_SET);
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() seconds timestamp");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap (uint32_t (1234)), "Swapped seconds timestamp written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() microseconds timestamp");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap (uint32_t (5678)), "Swapped microseconds timestamp written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() included length");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap (uint32_t (43)), "Swapped included length written incorrectly");
+
+ result = fread (&val32, sizeof(val32), 1, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 1, "Unable to fread() actual length");
+ NS_TEST_ASSERT_MSG_EQ (val32, Swap (uint32_t (128)), "Swapped Actual length written incorrectly");
+
+ //
+ // Take a look and see what went out into the file. The packet data
+ // should be unchanged (unswapped).
+ //
+ result = fread (bufferIn, 1, 43, p);
+ NS_TEST_ASSERT_MSG_EQ (result, 43, "Unable to fread() packet data of expected length");
+
+ for (uint32_t i = 0; i < 43; ++i)
+ {
+ NS_TEST_ASSERT_MSG_EQ (bufferIn[i], bufferOut[i], "Incorrect packet data written");
+ }
+
+ fclose (p);
+ p = 0;
+
+ //
+ // Let's see if the PcapFile object can figure out how to do the same thing and
+ // correctly read in a packet. The record header info should come back to us
+ // swapped back into correct form.
+ //
+ err = f.Open (m_testFilename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << m_testFilename << ", \"r\") of existing good file returns error");
+
+ err = f.Read (bufferIn, sizeof(bufferIn), tsSec, tsUsec, inclLen, origLen, readLen);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Read() of known good packet returns error");
+ NS_TEST_ASSERT_MSG_EQ (tsSec, 1234, "Incorrectly read seconds timestap from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (tsUsec, 5678, "Incorrectly read microseconds timestap from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (inclLen, 43, "Incorrectly read included length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (origLen, 128, "Incorrectly read original length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (readLen, 43, "Incorrectly constructed actual read length from known good packet given buffer size");
+
+ //
+ // Did the data come back correctly (unchanged / unswapped)?
+ //
+ for (uint32_t i = 0; i < 43; ++i)
+ {
+ NS_TEST_ASSERT_MSG_EQ (bufferIn[i], bufferOut[i], "Incorrect packet data read from known good packet");
+ }
+
+ f.Close ();
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Pcap File Object can read out the contents
+// of a known good pcap file.
+// ===========================================================================
+class ReadFileTestCase : public TestCase
+{
+public:
+ ReadFileTestCase ();
+ virtual ~ReadFileTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ std::string m_testFilename;
+};
+
+ReadFileTestCase::ReadFileTestCase ()
+ : TestCase ("Check to see that PcapFile can read out a known good pcap file")
+{
+}
+
+ReadFileTestCase::~ReadFileTestCase ()
+{
+}
+
+void
+ReadFileTestCase::DoSetup (void)
+{
+}
+
+void
+ReadFileTestCase::DoTeardown (void)
+{
+}
+
+const uint32_t N_KNOWN_PACKETS = 6;
+const uint32_t N_PACKET_BYTES = 16;
+
+typedef struct PACKET_ENTRY {
+ uint32_t tsSec;
+ uint32_t tsUsec;
+ uint32_t inclLen;
+ uint32_t origLen;
+ uint16_t data[N_PACKET_BYTES];
+} PacketEntry;
+
+PacketEntry knownPackets[] = {
+ {2, 3696, 46, 46, {0x0001, 0x0800, 0x0604, 0x0001, 0x0000, 0x0000, 0x0003, 0x0a01,
+ 0x0201, 0xffff, 0xffff, 0xffff, 0x0a01, 0x0204, 0x0000, 0x0000}},
+ {2, 3707, 46, 46, {0x0001, 0x0800, 0x0604, 0x0002, 0x0000, 0x0000, 0x0006, 0x0a01,
+ 0x0204, 0x0000, 0x0000, 0x0003, 0x0a01, 0x0201, 0x0000, 0x0000}},
+ {2, 3801, 1070, 1070, {0x4500, 0x041c, 0x0000, 0x0000, 0x3f11, 0x0000, 0x0a01, 0x0101,
+ 0x0a01, 0x0204, 0xc001, 0x0009, 0x0408, 0x0000, 0x0000, 0x0000}},
+ {2, 3811, 46, 46, {0x0001, 0x0800, 0x0604, 0x0001, 0x0000, 0x0000, 0x0006, 0x0a01,
+ 0x0204, 0xffff, 0xffff, 0xffff, 0x0a01, 0x0201, 0x0000, 0x0000}},
+ {2, 3822, 46, 46, {0x0001, 0x0800, 0x0604, 0x0002, 0x0000, 0x0000, 0x0003, 0x0a01,
+ 0x0201, 0x0000, 0x0000, 0x0006, 0x0a01, 0x0204, 0x0000, 0x0000}},
+ {2, 3915, 1070, 1070, {0x4500, 0x041c, 0x0000, 0x0000, 0x4011, 0x0000, 0x0a01, 0x0204,
+ 0x0a01, 0x0101, 0x0009, 0xc001, 0x0408, 0x0000, 0x0000, 0x0000}}
+};
+
+
+bool
+ReadFileTestCase::DoRun (void)
+{
+ PcapFile f;
+
+ //
+ //
+ std::string filename = NS_TEST_SOURCEDIR + "known.pcap";
+ bool err = f.Open (filename, "r");
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Open (" << filename << ", \"w\") returns error");
+
+ //
+ // We are going to read out the file header and all of the packets to make
+ // sure that we read what we know, a priori, to be there.
+ //
+ // The packet data was gotten using "tcpdump -nn -tt -r known.pcap -x"
+ // and the timestamp and first 32 bytes of the resulting dump were
+ // duplicated in the structure above.
+ //
+ uint8_t data[N_PACKET_BYTES];
+ uint32_t tsSec, tsUsec, inclLen, origLen, readLen;
+
+ PacketEntry *p = knownPackets;
+
+ for (uint32_t i = 0; i < N_KNOWN_PACKETS; ++i, ++p)
+ {
+ err = f.Read (data, sizeof(data), tsSec, tsUsec, inclLen, origLen, readLen);
+ NS_TEST_ASSERT_MSG_EQ (err, false, "Read() of known good pcap file returns error");
+ NS_TEST_ASSERT_MSG_EQ (tsSec, p->tsSec, "Incorrectly read seconds timestap from known good pcap file");
+ NS_TEST_ASSERT_MSG_EQ (tsUsec, p->tsUsec, "Incorrectly read microseconds timestap from known good pcap file");
+ NS_TEST_ASSERT_MSG_EQ (inclLen, p->inclLen, "Incorrectly read included length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (origLen, p->origLen, "Incorrectly read original length from known good packet");
+ NS_TEST_ASSERT_MSG_EQ (readLen, N_PACKET_BYTES, "Incorrect actual read length from known good packet given buffer size");
+ }
+
+ //
+ // The file should now be at EOF since we've read all of the packets.
+ // Another packet read should return an error.
+ //
+ err = f.Read (data, 1, tsSec, tsUsec, inclLen, origLen, readLen);
+ NS_TEST_ASSERT_MSG_EQ (err, true, "Read() of known good pcap file at EOF does not return error");
+
+ f.Close ();
+
+ return false;
+}
+
+class PcapFileTestSuite : public TestSuite
+{
+public:
+ PcapFileTestSuite ();
+};
+
+PcapFileTestSuite::PcapFileTestSuite ()
+ : TestSuite ("pcap-file-object", UNIT)
+{
+ AddTestCase (new WriteModeCreateTestCase);
+ AddTestCase (new ReadModeCreateTestCase);
+ AddTestCase (new AppendModeCreateTestCase);
+ AddTestCase (new FileHeaderTestCase);
+ AddTestCase (new RecordHeaderTestCase);
+ AddTestCase (new ReadFileTestCase);
+}
+
+PcapFileTestSuite pcapFileTestSuite;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common/pcap-file.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 University of Washington
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "pcap-file.h"
+
+//
+// This file is used as part of the ns-3 test framework, so please refrain from
+// adding any ns-3 specific constructs such as Packet to this file.
+//
+namespace ns3 {
+
+const uint32_t MAGIC = 0xa1b2c3d4; /**< Magic number identifying standard pcap file format */
+const uint32_t SWAPPED_MAGIC = 0xd4c3b2a1; /**< Looks this way if byte swapping is required */
+
+const uint32_t NS_MAGIC = 0xa1b23cd4; /**< Magic number identifying nanosec resolution pcap file format */
+const uint32_t NS_SWAPPED_MAGIC = 0xd43cb2a1; /**< Looks this way if byte swapping is required */
+
+const uint16_t VERSION_MAJOR = 2; /**< Major version of supported pcap file format */
+const uint16_t VERSION_MINOR = 4; /**< Minor version of supported pcap file format */
+const int32_t SIGFIGS_DEFAULT = 0; /**< Significant figures for timestamps (libpcap doesn't even bother) */
+
+PcapFile::PcapFile ()
+ : m_filename (""),
+ m_filePtr (0),
+ m_haveFileHeader (false),
+ m_swapMode (false)
+{
+}
+
+PcapFile::~PcapFile ()
+{
+ Close ();
+}
+
+void
+PcapFile::Close (void)
+{
+ if (m_filePtr)
+ {
+ fclose (m_filePtr);
+ }
+ m_filePtr = 0;
+ m_filename = "";
+ m_haveFileHeader = false;
+}
+
+uint32_t
+PcapFile::GetMagic (void)
+{
+ return m_fileHeader.m_magicNumber;
+}
+
+uint16_t
+PcapFile::GetVersionMajor (void)
+{
+ return m_fileHeader.m_versionMajor;
+}
+
+uint16_t
+PcapFile::GetVersionMinor (void)
+{
+ return m_fileHeader.m_versionMinor;
+}
+
+int32_t
+PcapFile::GetTimeZoneOffset (void)
+{
+ return m_fileHeader.m_zone;
+}
+
+uint32_t
+PcapFile::GetSigFigs (void)
+{
+ return m_fileHeader.m_sigFigs;
+}
+
+uint32_t
+PcapFile::GetSnapLen (void)
+{
+ return m_fileHeader.m_snapLen;
+}
+
+uint32_t
+PcapFile::GetDataLinkType (void)
+{
+ return m_fileHeader.m_type;
+}
+
+bool
+PcapFile::GetSwapMode (void)
+{
+ return m_swapMode;
+}
+
+uint8_t
+PcapFile::Swap (uint8_t val)
+{
+ return val;
+}
+
+uint16_t
+PcapFile::Swap (uint16_t val)
+{
+ return ((val >> 8) & 0x00ff) | ((val << 8) & 0xff00);
+}
+
+uint32_t
+PcapFile::Swap (uint32_t val)
+{
+ return ((val >> 24) & 0x000000ff) | ((val >> 8) & 0x0000ff00) | ((val << 8) & 0x00ff0000) | ((val << 24) & 0xff000000);
+}
+
+void
+PcapFile::Swap (PcapFileHeader *from, PcapFileHeader *to)
+{
+ to->m_magicNumber = Swap (from->m_magicNumber);
+ to->m_versionMajor = Swap (from->m_versionMajor);
+ to->m_versionMinor = Swap (from->m_versionMinor);
+ to->m_zone = Swap (uint32_t(from->m_zone));
+ to->m_sigFigs = Swap (from->m_sigFigs);
+ to->m_snapLen = Swap (from->m_snapLen);
+ to->m_type = Swap (from->m_type);
+}
+
+void
+PcapFile::Swap (PcapRecordHeader *from, PcapRecordHeader *to)
+{
+ to->m_tsSec = Swap (from->m_tsSec);
+ to->m_tsUsec = Swap (from->m_tsUsec);
+ to->m_inclLen = Swap (from->m_inclLen);
+ to->m_origLen = Swap (from->m_origLen);
+}
+
+bool
+PcapFile::WriteFileHeader (void)
+{
+ //
+ // If we're initializing the file, we need to write the pcap file header
+ // at the start of the file.
+ //
+ int result = fseek (m_filePtr, 0, SEEK_SET);
+ if (result)
+ {
+ return true;
+ }
+
+ //
+ // We have the ability to write out the pcap file header in a foreign endian
+ // format, so we need a temp place to swap on the way out.
+ //
+ PcapFileHeader header;
+
+ //
+ // the pointer headerOut selects either the swapped or non-swapped version of
+ // the pcap file header.
+ //
+ PcapFileHeader *headerOut = 0;
+
+ if (m_swapMode == false)
+ {
+ headerOut = &m_fileHeader;
+ }
+ else
+ {
+ Swap (&m_fileHeader, &header);
+ headerOut = &header;
+ }
+
+ //
+ // Watch out for memory alignment differences between machines, so write
+ // them all individually.
+ //
+ result = 0;
+
+ result |= (fwrite (&headerOut->m_magicNumber, sizeof(headerOut->m_magicNumber), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_versionMajor, sizeof(headerOut->m_versionMajor), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_versionMinor, sizeof(headerOut->m_versionMinor), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_zone, sizeof(headerOut->m_zone), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_sigFigs, sizeof(headerOut->m_sigFigs), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_snapLen, sizeof(headerOut->m_snapLen), 1, m_filePtr) != 1);
+ result |= (fwrite (&headerOut->m_type, sizeof(headerOut->m_type), 1, m_filePtr) != 1);
+
+ //
+ // If any of the fwrites above did not succeed in writinging the correct
+ // number of objects, result will be nonzero and will indicate an error.
+ //
+ return result != 0;
+}
+
+bool
+PcapFile::ReadAndVerifyFileHeader (void)
+{
+ //
+ // Pcap file header is always at the start of the file
+ //
+ int result = fseek (m_filePtr, 0, SEEK_SET);
+ if (result)
+ {
+ return true;
+ }
+
+ //
+ // Watch out for memory alignment differences between machines, so read
+ // them all individually.
+ //
+ result = 0;
+
+ result |= (fread (&m_fileHeader.m_magicNumber, sizeof(m_fileHeader.m_magicNumber), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_versionMajor, sizeof(m_fileHeader.m_versionMajor), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_versionMinor, sizeof(m_fileHeader.m_versionMinor), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_zone, sizeof(m_fileHeader.m_zone), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_sigFigs, sizeof(m_fileHeader.m_sigFigs), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_snapLen, sizeof(m_fileHeader.m_snapLen), 1, m_filePtr) != 1);
+ result |= (fread (&m_fileHeader.m_type, sizeof(m_fileHeader.m_type), 1, m_filePtr) != 1);
+
+ //
+ // If any of the freads above did not succeed in reading the correct number of
+ // objects, result will be nonzero.
+ //
+ if (result)
+ {
+ return true;
+ }
+
+ //
+ // There are four possible magic numbers that can be there. Normal and byte
+ // swapped versions of the standard magic number, and normal and byte swapped
+ // versions of the magic number indicating nanosecond resolution timestamps.
+ //
+ if (m_fileHeader.m_magicNumber != MAGIC && m_fileHeader.m_magicNumber != SWAPPED_MAGIC &&
+ m_fileHeader.m_magicNumber != NS_MAGIC && m_fileHeader.m_magicNumber != NS_SWAPPED_MAGIC)
+ {
+ return true;
+ }
+
+ //
+ // If the magic number is swapped, then we can assume that everything else we read
+ // is swapped.
+ //
+ m_swapMode = (m_fileHeader.m_magicNumber == SWAPPED_MAGIC || m_fileHeader.m_magicNumber == NS_SWAPPED_MAGIC) ? true : false;
+
+ if (m_swapMode)
+ {
+ Swap (&m_fileHeader, &m_fileHeader);
+ }
+
+ //
+ // We only deal with one version of the pcap file format.
+ //
+ if (m_fileHeader.m_versionMajor != VERSION_MAJOR || m_fileHeader.m_versionMinor != VERSION_MINOR)
+ {
+ return true;
+ }
+
+ //
+ // A quick test of reasonablness for the time zone offset corresponding to
+ // a real place on the planet.
+ //
+ if (m_fileHeader.m_zone < -12 || m_fileHeader.m_zone > 12)
+ {
+ return true;
+ }
+
+ m_haveFileHeader = true;
+ return false;
+}
+
+bool
+PcapFile::Open (std::string const &filename, std::string const &mode)
+{
+ //
+ // If opening a new file, implicit close of any existing file required.
+ //
+ Close ();
+
+ //
+ // All pcap files are binary files, so we just do this automatically.
+ //
+ std::string realMode = mode + "b";
+
+ //
+ // Our modes may be subtly different from the standard fopen semantics since
+ // we need to have a pcap file header to succeed in some cases; so we need
+ // to process different modes according to our own definitions of the modes.
+ //
+ // In the case of read modes, we must read, check and save the pcap file
+ // header as well as just opening the file.
+ //
+ // In the case of write modes, we just pass the call on through to the
+ // library.
+ //
+ // In the case of append modes, we change the semantics to require the
+ // given file to exist. We can't just create a file since we can't make up
+ // a pcap file header on our own.
+ //
+ if (realMode == "rb" || realMode == "r+b")
+ {
+ m_filePtr = fopen (filename.c_str (), realMode.c_str ());
+ if (m_filePtr == 0)
+ {
+ return true;
+ }
+ m_filename = filename;
+ return ReadAndVerifyFileHeader ();
+ }
+ else if (realMode == "wb" || realMode == "w+b")
+ {
+ m_filePtr = fopen (filename.c_str (), realMode.c_str ());
+ if (m_filePtr)
+ {
+ m_filename = filename;
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else if (realMode == "ab" || realMode == "a+b")
+ {
+ //
+ // Remember that semantics for append are different here. We never create
+ // a file since we can't make up a pcap file header. We first have to
+ // open the file in read-only mode and check to see that it exists and
+ // read the file header. If this all works out, then we can go ahead and
+ // open the file in append mode and seek to the end (imlicitly).
+ //
+ m_filePtr = fopen (filename.c_str (), "rb");
+ if (m_filePtr == 0)
+ {
+ return true;
+ }
+
+ bool result = ReadAndVerifyFileHeader ();
+ if (result == true)
+ {
+ Close ();
+ return true;
+ }
+
+ //
+ // We have a properly initialized file and have the pcap file header
+ // loaded and checked. This means that the file meets all of the
+ // critera for opening in append mode, but the file is in read-only mode
+ // now -- we must close it and open it in the correct mode.
+ //
+ fclose (m_filePtr);
+ m_filePtr = 0;
+
+ m_filePtr = fopen (filename.c_str (), realMode.c_str ());
+ if (m_filePtr == 0)
+ {
+ return true;
+ }
+
+ m_filename = filename;
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool
+PcapFile::Init (uint32_t dataLinkType, uint32_t snapLen, int32_t timeZoneCorrection, bool swapMode)
+{
+ //
+ // Initialize the in-memory file header.
+ //
+ m_fileHeader.m_magicNumber = MAGIC;
+ m_fileHeader.m_versionMajor = VERSION_MAJOR;
+ m_fileHeader.m_versionMinor = VERSION_MINOR;
+ m_fileHeader.m_zone = timeZoneCorrection;
+ m_fileHeader.m_sigFigs = 0;
+ m_fileHeader.m_snapLen = snapLen;
+ m_fileHeader.m_type = dataLinkType;
+
+ m_haveFileHeader = true;
+ m_swapMode = swapMode;
+
+ return WriteFileHeader ();
+}
+
+bool
+PcapFile::Write (uint32_t tsSec, uint32_t tsUsec, uint8_t const * const data, uint32_t totalLen)
+{
+ if (m_haveFileHeader == false)
+ {
+ return true;
+ }
+
+ uint32_t inclLen = totalLen > m_fileHeader.m_snapLen ? m_fileHeader.m_snapLen : totalLen;
+
+ PcapRecordHeader header;
+ header.m_tsSec = tsSec;
+ header.m_tsUsec = tsUsec;
+ header.m_inclLen = inclLen;
+ header.m_origLen = totalLen;
+
+ if (m_swapMode)
+ {
+ Swap (&header, &header);
+ }
+
+ //
+ // Watch out for memory alignment differences between machines, so write
+ // them all individually.
+ //
+ uint32_t result = 0;
+
+ result |= (fwrite (&header.m_tsSec, sizeof(header.m_tsSec), 1, m_filePtr) != 1);
+ result |= (fwrite (&header.m_tsUsec, sizeof(header.m_tsUsec), 1, m_filePtr) != 1);
+ result |= (fwrite (&header.m_inclLen, sizeof(header.m_inclLen), 1, m_filePtr) != 1);
+ result |= (fwrite (&header.m_origLen, sizeof(header.m_origLen), 1, m_filePtr) != 1);
+
+ result |= fwrite (data, 1, inclLen, m_filePtr) != inclLen;
+
+ return result != 0;
+}
+
+bool
+PcapFile::Read (
+ uint8_t * const data,
+ uint32_t maxBytes,
+ uint32_t &tsSec,
+ uint32_t &tsUsec,
+ uint32_t &inclLen,
+ uint32_t &origLen,
+ uint32_t &readLen)
+{
+ if (m_haveFileHeader == false)
+ {
+ return true;
+ }
+
+ PcapRecordHeader header;
+
+ //
+ // Watch out for memory alignment differences between machines, so read
+ // them all individually.
+ //
+ uint32_t result = 0;
+
+ result |= (fread (&header.m_tsSec, sizeof(header.m_tsSec), 1, m_filePtr) != 1);
+ result |= (fread (&header.m_tsUsec, sizeof(header.m_tsUsec), 1, m_filePtr) != 1);
+ result |= (fread (&header.m_inclLen, sizeof(header.m_inclLen), 1, m_filePtr) != 1);
+ result |= (fread (&header.m_origLen, sizeof(header.m_origLen), 1, m_filePtr) != 1);
+
+ //
+ // If any of the freads above did not succeed in reading the correct number of
+ // objects, result will be nonzero.
+ //
+ if (result)
+ {
+ return true;
+ }
+
+ if (m_swapMode)
+ {
+ Swap (&header, &header);
+ }
+
+ tsSec = header.m_tsSec;
+ tsUsec = header.m_tsUsec;
+ inclLen = header.m_inclLen;
+ origLen = header.m_origLen;
+
+ //
+ // We don't always want to force the client to keep a maximum length buffer
+ // around so we allow her to specify a minimum number of bytes to read.
+ // Usually 64 bytes is enough information to print all of the headers, so
+ // it isn't typically necessary to read all thousand bytes of an echo packet,
+ // for example, to figure out what is going on.
+ //
+ readLen = maxBytes < header.m_inclLen ? maxBytes : header.m_inclLen;
+ result = fread (data, 1, readLen, m_filePtr) != readLen;
+ if (result)
+ {
+ return result;
+ }
+
+ //
+ // To keep the file pointer pointed in the right place, however, we always
+ // need to account for the entire packet as stored originally.
+ //
+ if (readLen < header.m_inclLen)
+ {
+ uint64_t pos = ftell (m_filePtr);
+ int result = fseek (m_filePtr, pos + header.m_inclLen - readLen, SEEK_SET);
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} //namespace ns3
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common/pcap-file.h Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 University of Washington
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PCAP_FILE_H
+#define PCAP_FILE_H
+
+#include <string>
+
+namespace ns3 {
+
+/*
+ * A class representing a pcap file. This allows easy creation, writing and
+ * reading of files composed of stored packets; which may be viewed using
+ * standard tools.
+ */
+
+class PcapFile
+{
+public:
+ static const int32_t ZONE_DEFAULT = 0; /**< Time zone offset for current location */
+ static const uint32_t SNAPLEN_DEFAULT = 65535; /**< Default value for maximum octets to save per packet */
+
+public:
+ PcapFile ();
+ ~PcapFile ();
+
+ /**
+ * Create a new pcap file or open an existing pcap file. Semantics are
+ * similar to the C standard library function \c fopen, but differ in that
+ * positions in the file are based on packets not characters. For example
+ * if the file is opened for reading, the file position indicator (seek
+ * position) points to the beginning of the first packet in the file, not
+ * zero (which would point to the start of the pcap header).
+ *
+ * Possible modes are:
+ *
+ * \verbatim
+ * "r": Open a file for reading. The file must exist. The pcap header
+ * is assumed to exist in the file and will be read and checked.
+ * The file seek position indicator is set to point to the first
+ * packet on exit.
+ *
+ * "w": Create an empty file for writing. If a file with the same name
+ * already exists its content is erased and the file is treated as a
+ * new empty pcap file. The file is assumed not to have a pcap
+ * header and the caller is responsible for calling Init before saving
+ * any packet data. The file seek position indicator is set to point
+ * to the beginning of the file on exit since there will be no pcap
+ * header.
+ *
+ * "a": Append to an existing file. This mode allows for adding packet data
+ * to the end of an existing pcap file. The file must exist and have a
+ * valid pcap header written (N.B. this is different from standard fopen
+ * semantics). The file seek position indicator is set to point
+ * to the end of the file on exit.
+ *
+ * "r+": Open a file for update -- both reading and writing. The file must
+ * exist. The pcap header is assumed to have been written to the
+ * file and will be read and checked. The file seek position indicator
+ * is set to point to the first packet on exit.
+ *
+ * "w+": Create an empty file for both reading and writing. If a file with
+ * the same name already exists, its content is erased and the file is
+ * treated as a new empty pcap file. Since this new file will not have
+ * a pcap header, the caller is responsible for calling Init before
+ * saving any packet data. On exit, the file seek position indicator is
+ * set to point to the beginning of the file.
+ *
+ * "a+" Open a file for reading and appending. The file must exist and have a
+ * valid pcap header written (N.B. this is different from standard fopen
+ * semantics). The file seek position indicator is set to point
+ * to the end of the file on exit. Existing content is preserved.
+ * \endverbatim
+ *
+ * Since a pcap file is always a binary file, the file type is automatically
+ * selected as a binary file. For example, providing a mode string "a+"
+ * results in the underlying OS file being opened in "a+b" mode.
+ *
+ * \param filename String containing the name of the file.
+ *
+ * \param mode String containing the access mode for the file.
+ *
+ * \returns Error indication that should be interpreted as, "did an error
+ * happen"? That is, the method returns false if the open succeeds, true
+ * otherwise. The errno variable will be set by the OS to to provide a
+ * more descriptive failure indication.
+ */
+ bool Open (std::string const &filename, std::string const &mode);
+
+ void Close (void);
+
+ /**
+ * Initialize the pcap file associated with this object. This file must have
+ * been previously opened with write permissions.
+ *
+ * \param dataLinkType A data link type as defined in the pcap library. If
+ * you want to make resulting pcap files visible in existing tools, the
+ * data link type must match existing definitions, such as PCAP_ETHERNET,
+ * PCAP_PPP, PCAP_80211, etc. If you are storing different kinds of packet
+ * data, such as naked TCP headers, you are at liberty to locally define your
+ * own data link types. According to the pcap-linktype man page, "well-known"
+ * pcap linktypes range from 0 to 177. If you use a large random number for
+ * your type, chances are small for a collision.
+ *
+ * \param snapLen An optional maximum size for packets written to the file.
+ * Defaults to 65535. If packets exceed this length they are truncated.
+ *
+ * \param timeZoneCorrection An integer describing the offset of your local
+ * time zone from UTC/GMT. For example, Pacific Standard Time in the US is
+ * GMT-8, so one would enter -8 for that correction. Defaults to 0 (UTC).
+ *
+ * \returns false if the open succeeds, true otherwise.
+ *
+ * \warning Calling this method on an existing file will result in the loss
+ * any existing data.
+ */
+ bool Init (uint32_t dataLinkType,
+ uint32_t snapLen = SNAPLEN_DEFAULT,
+ int32_t timeZoneCorrection = ZONE_DEFAULT,
+ bool swapMode = false);
+
+ bool Write (uint32_t tsSec, uint32_t tsUsec, uint8_t const * const data, uint32_t totalLen);
+
+ bool Read (uint8_t * const data,
+ uint32_t maxBytes,
+ uint32_t &tsSec,
+ uint32_t &tsUsec,
+ uint32_t &inclLen,
+ uint32_t &origLen,
+ uint32_t &readLen);
+
+ bool GetSwapMode (void);
+
+ uint32_t GetMagic (void);
+ uint16_t GetVersionMajor (void);
+ uint16_t GetVersionMinor (void);
+ int32_t GetTimeZoneOffset (void);
+ uint32_t GetSigFigs (void);
+ uint32_t GetSnapLen (void);
+ uint32_t GetDataLinkType (void);
+
+private:
+ typedef struct {
+ uint32_t m_magicNumber; /**< Magic number identifying this as a pcap file */
+ uint16_t m_versionMajor; /**< Major version identifying the version of pcap used in this file */
+ uint16_t m_versionMinor; /**< Minor version identifying the version of pcap used in this file */
+ int32_t m_zone; /**< Time zone correction to be applied to timestamps of packets */
+ uint32_t m_sigFigs; /**< Unused by pretty much everybody */
+ uint32_t m_snapLen; /**< Maximum length of packet data stored in records */
+ uint32_t m_type; /**< Data link type of packet data */
+ } PcapFileHeader;
+
+ typedef struct {
+ uint32_t m_tsSec; /**< seconds part of timestamp */
+ uint32_t m_tsUsec; /**< microseconds part of timestamp (nsecs for PCAP_NSEC_MAGIC) */
+ uint32_t m_inclLen; /**< number of octets of packet saved in file */
+ uint32_t m_origLen; /**< actual length of original packet */
+ } PcapRecordHeader;
+
+ uint8_t Swap (uint8_t val);
+ uint16_t Swap (uint16_t val);
+ uint32_t Swap (uint32_t val);
+ void Swap (PcapFileHeader *from, PcapFileHeader *to);
+ void Swap (PcapRecordHeader *from, PcapRecordHeader *to);
+
+ bool WriteFileHeader (void);
+ bool ReadAndVerifyFileHeader (void);
+
+ std::string m_filename;
+ FILE *m_filePtr;
+ PcapFileHeader m_fileHeader;
+ bool m_haveFileHeader;
+ bool m_swapMode;
+};
+
+}//namespace ns3
+
+#endif /* PCAP_FILE_H */
--- a/src/common/wscript Thu Sep 10 11:41:33 2009 +0100
+++ b/src/common/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -18,6 +18,8 @@
'tag-buffer.cc',
'packet-tag-list.cc',
'ascii-writer.cc',
+ 'pcap-file.cc',
+ 'pcap-file-test-suite.cc',
]
headers = bld.new_task_gen('ns3header')
@@ -38,4 +40,5 @@
'packet-tag-list.h',
'ascii-writer.h',
'sgi-hashmap.h',
+ 'pcap-file.h',
]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/names-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,975 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include "names.h"
+
+using namespace ns3;
+
+// ===========================================================================
+// Cook up a couple of simple object class that we can use in the object
+// naming tests. They do nothing but be of the right type.
+// ===========================================================================
+class TestObject : public Object
+{
+public:
+ static TypeId GetTypeId (void)
+ {
+ static TypeId tid = TypeId ("TestObject")
+ .SetParent (Object::GetTypeId ())
+ .HideFromDocumentation ()
+ .AddConstructor<TestObject> ();
+ return tid;
+ }
+ TestObject () {}
+ virtual void Dispose (void) {}
+};
+
+class AlternateTestObject : public Object
+{
+public:
+ static TypeId GetTypeId (void)
+ {
+ static TypeId tid = TypeId ("AlternateTestObject")
+ .SetParent (Object::GetTypeId ())
+ .HideFromDocumentation ()
+ .AddConstructor<AlternateTestObject> ();
+ return tid;
+ }
+ AlternateTestObject () {}
+ virtual void Dispose (void) {}
+};
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can do its most basic
+// job and add associations between Objects using the lowest level add
+// function, which is:
+//
+// Add (Ptr<Object> context, std::string name, Ptr<Object> object);
+//
+// All other add functions will just translate into this form, so this is the
+// most basic Add functionality.
+// ===========================================================================
+class BasicAddTestCase : public TestCase
+{
+public:
+ BasicAddTestCase ();
+ virtual ~BasicAddTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+BasicAddTestCase::BasicAddTestCase ()
+ : TestCase ("Check low level Names::Add and Names::FindName functionality")
+{
+}
+
+BasicAddTestCase::~BasicAddTestCase ()
+{
+}
+
+void
+BasicAddTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+BasicAddTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add (Ptr<Object> (0, false), "Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add (Ptr<Object> (0, false), "Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add (objectOne, "Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add (objectTwo, "Child", childOfObjectTwo);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name One", "Could not Names::Add and Names::FindName an Object");
+
+ found = Names::FindName (objectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name Two", "Could not Names::Add and Names::FindName a second Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ found = Names::FindName (childOfObjectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can correctly use a
+// string context in the most basic ways
+//
+// Add (std::string context, std::string name, Ptr<Object> object);
+//
+// High level path-based functions will translate into this form, so this is
+// the second most basic Add functionality.
+// ===========================================================================
+class StringContextAddTestCase : public TestCase
+{
+public:
+ StringContextAddTestCase ();
+ virtual ~StringContextAddTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+StringContextAddTestCase::StringContextAddTestCase ()
+ : TestCase ("Check string context Names::Add and Names::FindName functionality")
+
+{
+}
+
+StringContextAddTestCase::~StringContextAddTestCase ()
+{
+}
+
+void
+StringContextAddTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+StringContextAddTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names", "Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names", "Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name One", "Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name Two", "Child", childOfObjectTwo);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name One", "Could not Names::Add and Names::FindName an Object");
+
+ found = Names::FindName (objectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name Two", "Could not Names::Add and Names::FindName a second Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ found = Names::FindName (childOfObjectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can correctly use a
+// fully qualified path to add assocations
+//
+// Add (std::string name, Ptr<Object> object);
+// ===========================================================================
+class FullyQualifiedAddTestCase : public TestCase
+{
+public:
+ FullyQualifiedAddTestCase ();
+ virtual ~FullyQualifiedAddTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+FullyQualifiedAddTestCase::FullyQualifiedAddTestCase ()
+ : TestCase ("Check fully qualified path Names::Add and Names::FindName functionality")
+
+{
+}
+
+FullyQualifiedAddTestCase::~FullyQualifiedAddTestCase ()
+{
+}
+
+void
+FullyQualifiedAddTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+FullyQualifiedAddTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name Two/Child", childOfObjectTwo);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name One", "Could not Names::Add and Names::FindName an Object");
+
+ found = Names::FindName (objectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name Two", "Could not Names::Add and Names::FindName a second Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ found = Names::FindName (childOfObjectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can correctly use a
+// relative path to add assocations. This functionality is provided as a
+// convenience so clients don't always have to provide the name service
+// namespace name in all of their strings.
+//
+//
+// Add (std::string name, Ptr<Object> object);
+// ===========================================================================
+class RelativeAddTestCase : public TestCase
+{
+public:
+ RelativeAddTestCase ();
+ virtual ~RelativeAddTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+RelativeAddTestCase::RelativeAddTestCase ()
+ : TestCase ("Check relative path Names::Add and Names::FindName functionality")
+
+{
+}
+
+RelativeAddTestCase::~RelativeAddTestCase ()
+{
+}
+
+void
+RelativeAddTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+RelativeAddTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two/Child", childOfObjectTwo);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name One", "Could not Names::Add and Names::FindName an Object");
+
+ found = Names::FindName (objectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name Two", "Could not Names::Add and Names::FindName a second Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ found = Names::FindName (childOfObjectTwo);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can rename objects in
+// its most basic way, which is
+//
+// Rename (Ptr<Object> context, std::string oldname, std::string newname);
+//
+// All other rename functions will just translate into this form, so this is the
+// most basic rename functionality.
+// ===========================================================================
+class BasicRenameTestCase : public TestCase
+{
+public:
+ BasicRenameTestCase ();
+ virtual ~BasicRenameTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+BasicRenameTestCase::BasicRenameTestCase ()
+ : TestCase ("Check low level Names::Rename functionality")
+{
+}
+
+BasicRenameTestCase::~BasicRenameTestCase ()
+{
+}
+
+void
+BasicRenameTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+BasicRenameTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add (Ptr<Object> (0, false), "Name", objectOne);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add (objectOne, "Child", childOfObjectOne);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name", "Could not Names::Add and Names::FindName an Object");
+
+ Names::Rename (Ptr<Object> (0, false), "Name", "New Name");
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Name", "Could not Names::Rename an Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ Names::Rename (objectOne, "Child", "New Child");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Child", "Could not Names::Rename a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can rename objects
+// using a string context
+//
+// Rename (std::string context, std::string oldname, std::string newname);
+// ===========================================================================
+class StringContextRenameTestCase : public TestCase
+{
+public:
+ StringContextRenameTestCase ();
+ virtual ~StringContextRenameTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+StringContextRenameTestCase::StringContextRenameTestCase ()
+ : TestCase ("Check string context-based Names::Rename functionality")
+{
+}
+
+StringContextRenameTestCase::~StringContextRenameTestCase ()
+{
+}
+
+void
+StringContextRenameTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+StringContextRenameTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names", "Name", objectOne);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name", "Child", childOfObjectOne);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name", "Could not Names::Add and Names::FindName an Object");
+
+ Names::Rename ("/Names", "Name", "New Name");
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Name", "Could not Names::Rename an Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ Names::Rename ("/Names/New Name", "Child", "New Child");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Child", "Could not Names::Rename a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can rename objects
+// using a fully qualified path name
+//
+// Rename (std::string oldpath, std::string newname);
+// ===========================================================================
+class FullyQualifiedRenameTestCase : public TestCase
+{
+public:
+ FullyQualifiedRenameTestCase ();
+ virtual ~FullyQualifiedRenameTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+FullyQualifiedRenameTestCase::FullyQualifiedRenameTestCase ()
+ : TestCase ("Check fully qualified path Names::Rename functionality")
+{
+}
+
+FullyQualifiedRenameTestCase::~FullyQualifiedRenameTestCase ()
+{
+}
+
+void
+FullyQualifiedRenameTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+FullyQualifiedRenameTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name", objectOne);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name/Child", childOfObjectOne);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name", "Could not Names::Add and Names::FindName an Object");
+
+ Names::Rename ("/Names/Name", "New Name");
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Name", "Could not Names::Rename an Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ Names::Rename ("/Names/New Name/Child", "New Child");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Child", "Could not Names::Rename a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can rename objects
+// using a relaltive path name
+//
+// Rename (std::string oldpath, std::string newname);
+// ===========================================================================
+class RelativeRenameTestCase : public TestCase
+{
+public:
+ RelativeRenameTestCase ();
+ virtual ~RelativeRenameTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+RelativeRenameTestCase::RelativeRenameTestCase ()
+ : TestCase ("Check relative path Names::Rename functionality")
+{
+}
+
+RelativeRenameTestCase::~RelativeRenameTestCase ()
+{
+}
+
+void
+RelativeRenameTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+RelativeRenameTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name", objectOne);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("Name/Child", childOfObjectOne);
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Name", "Could not Names::Add and Names::FindName an Object");
+
+ Names::Rename ("Name", "New Name");
+
+ found = Names::FindName (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Name", "Could not Names::Rename an Object");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "Child", "Could not Names::Add and Names::FindName a child Object");
+
+ Names::Rename ("New Name/Child", "New Child");
+
+ found = Names::FindName (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "New Child", "Could not Names::Rename a child Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can look up an object
+// and return its fully qualified path name
+//
+// FindPath (Ptr<Object> object);
+// ===========================================================================
+class FindPathTestCase : public TestCase
+{
+public:
+ FindPathTestCase ();
+ virtual ~FindPathTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+FindPathTestCase::FindPathTestCase ()
+ : TestCase ("Check Names::FindPath functionality")
+{
+}
+
+FindPathTestCase::~FindPathTestCase ()
+{
+}
+
+void
+FindPathTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+FindPathTestCase::DoRun (void)
+{
+ std::string found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name", objectOne);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name/Child", childOfObjectOne);
+
+ found = Names::FindPath (objectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "/Names/Name", "Could not Names::Add and Names::FindPath an Object");
+
+ found = Names::FindPath (childOfObjectOne);
+ NS_TEST_ASSERT_MSG_EQ (found, "/Names/Name/Child", "Could not Names::Add and Names::FindPath a child Object");
+
+ Ptr<TestObject> objectNotThere = CreateObject<TestObject> ();
+ found = Names::FindPath (objectNotThere);
+ NS_TEST_ASSERT_MSG_EQ (found, "", "Unexpectedly found a non-existent Object");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can find Objects using
+// the lowest level find function, which is:
+//
+// Find (Ptr<Object> context, std::string name);
+// ===========================================================================
+class BasicFindTestCase : public TestCase
+{
+public:
+ BasicFindTestCase ();
+ virtual ~BasicFindTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+BasicFindTestCase::BasicFindTestCase ()
+ : TestCase ("Check low level Names::Find functionality")
+{
+}
+
+BasicFindTestCase::~BasicFindTestCase ()
+{
+}
+
+void
+BasicFindTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+BasicFindTestCase::DoRun (void)
+{
+ Ptr<TestObject> found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two/Child", childOfObjectTwo);
+
+ found = Names::Find<TestObject> (Ptr<Object> (0, false), "Name One");
+ NS_TEST_ASSERT_MSG_EQ (found, objectOne, "Could not find a previously named Object via object context");
+
+ found = Names::Find<TestObject> (Ptr<Object> (0, false), "Name Two");
+ NS_TEST_ASSERT_MSG_EQ (found, objectTwo, "Could not find a previously named Object via object context");
+
+ found = Names::Find<TestObject> (objectOne, "Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectOne, "Could not find a previously named child Object via object context");
+
+ found = Names::Find<TestObject> (objectTwo, "Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectTwo, "Could not find a previously named child Object via object context");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can find Objects using
+// a string context-based find function, which is:
+//
+// Find (std::string context, std::string name);
+// ===========================================================================
+class StringContextFindTestCase : public TestCase
+{
+public:
+ StringContextFindTestCase ();
+ virtual ~StringContextFindTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+StringContextFindTestCase::StringContextFindTestCase ()
+ : TestCase ("Check string context-based Names::Find functionality")
+{
+}
+
+StringContextFindTestCase::~StringContextFindTestCase ()
+{
+}
+
+void
+StringContextFindTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+StringContextFindTestCase::DoRun (void)
+{
+ Ptr<TestObject> found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two/Child", childOfObjectTwo);
+
+ found = Names::Find<TestObject> ("/Names", "Name One");
+ NS_TEST_ASSERT_MSG_EQ (found, objectOne, "Could not find a previously named Object via string context");
+
+ found = Names::Find<TestObject> ("/Names", "Name Two");
+ NS_TEST_ASSERT_MSG_EQ (found, objectTwo, "Could not find a previously named Object via stribng context");
+
+ found = Names::Find<TestObject> ("/Names/Name One", "Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectOne, "Could not find a previously named child Object via string context");
+
+ found = Names::Find<TestObject> ("/Names/Name Two", "Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectTwo, "Could not find a previously named child Object via string context");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can find Objects using
+// a fully qualified path name-based find function, which is:
+//
+// Find (std::string name);
+// ===========================================================================
+class FullyQualifiedFindTestCase : public TestCase
+{
+public:
+ FullyQualifiedFindTestCase ();
+ virtual ~FullyQualifiedFindTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+FullyQualifiedFindTestCase::FullyQualifiedFindTestCase ()
+ : TestCase ("Check fully qualified path Names::Find functionality")
+{
+}
+
+FullyQualifiedFindTestCase::~FullyQualifiedFindTestCase ()
+{
+}
+
+void
+FullyQualifiedFindTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+FullyQualifiedFindTestCase::DoRun (void)
+{
+ Ptr<TestObject> found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("/Names/Name Two/Child", childOfObjectTwo);
+
+ found = Names::Find<TestObject> ("/Names/Name One");
+ NS_TEST_ASSERT_MSG_EQ (found, objectOne, "Could not find a previously named Object via string context");
+
+ found = Names::Find<TestObject> ("/Names/Name Two");
+ NS_TEST_ASSERT_MSG_EQ (found, objectTwo, "Could not find a previously named Object via stribng context");
+
+ found = Names::Find<TestObject> ("/Names/Name One/Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectOne, "Could not find a previously named child Object via string context");
+
+ found = Names::Find<TestObject> ("/Names/Name Two/Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectTwo, "Could not find a previously named child Object via string context");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can find Objects using
+// a relative path name-based find function, which is:
+//
+// Find (std::string name);
+// ===========================================================================
+class RelativeFindTestCase : public TestCase
+{
+public:
+ RelativeFindTestCase ();
+ virtual ~RelativeFindTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+RelativeFindTestCase::RelativeFindTestCase ()
+ : TestCase ("Check relative path Names::Find functionality")
+{
+}
+
+RelativeFindTestCase::~RelativeFindTestCase ()
+{
+}
+
+void
+RelativeFindTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+RelativeFindTestCase::DoRun (void)
+{
+ Ptr<TestObject> found;
+
+ Ptr<TestObject> objectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One", objectOne);
+
+ Ptr<TestObject> objectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two", objectTwo);
+
+ Ptr<TestObject> childOfObjectOne = CreateObject<TestObject> ();
+ Names::Add ("Name One/Child", childOfObjectOne);
+
+ Ptr<TestObject> childOfObjectTwo = CreateObject<TestObject> ();
+ Names::Add ("Name Two/Child", childOfObjectTwo);
+
+ found = Names::Find<TestObject> ("Name One");
+ NS_TEST_ASSERT_MSG_EQ (found, objectOne, "Could not find a previously named Object via string context");
+
+ found = Names::Find<TestObject> ("Name Two");
+ NS_TEST_ASSERT_MSG_EQ (found, objectTwo, "Could not find a previously named Object via stribng context");
+
+ found = Names::Find<TestObject> ("Name One/Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectOne, "Could not find a previously named child Object via string context");
+
+ found = Names::Find<TestObject> ("Name Two/Child");
+ NS_TEST_ASSERT_MSG_EQ (found, childOfObjectTwo, "Could not find a previously named child Object via string context");
+
+ return false;
+}
+
+// ===========================================================================
+// Test case to make sure that the Object Name Service can find Objects using
+// a second type.
+// ===========================================================================
+class AlternateFindTestCase : public TestCase
+{
+public:
+ AlternateFindTestCase ();
+ virtual ~AlternateFindTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+};
+
+AlternateFindTestCase::AlternateFindTestCase ()
+ : TestCase ("Check GetObject operation in Names::Find")
+{
+}
+
+AlternateFindTestCase::~AlternateFindTestCase ()
+{
+}
+
+void
+AlternateFindTestCase::DoTeardown (void)
+{
+ Names::Delete ();
+}
+
+bool
+AlternateFindTestCase::DoRun (void)
+{
+ Ptr<TestObject> testObject = CreateObject<TestObject> ();
+ Names::Add ("Test Object", testObject);
+
+ Ptr<AlternateTestObject> alternateTestObject = CreateObject<AlternateTestObject> ();
+ Names::Add ("Alternate Test Object", alternateTestObject);
+
+ Ptr<TestObject> foundTestObject;
+ Ptr<AlternateTestObject> foundAlternateTestObject;
+
+ foundTestObject = Names::Find<TestObject> ("Test Object");
+ NS_TEST_ASSERT_MSG_EQ (foundTestObject, testObject,
+ "Could not find a previously named TestObject via GetObject");
+
+ foundAlternateTestObject = Names::Find<AlternateTestObject> ("Alternate Test Object");
+ NS_TEST_ASSERT_MSG_EQ (foundAlternateTestObject, alternateTestObject,
+ "Could not find a previously named AlternateTestObject via GetObject");
+
+
+ foundAlternateTestObject = Names::Find<AlternateTestObject> ("Test Object");
+ NS_TEST_ASSERT_MSG_EQ (foundAlternateTestObject, 0,
+ "Unexpectedly able to GetObject<AlternateTestObject> on a TestObject");
+
+ foundTestObject = Names::Find<TestObject> ("Alternate Test Object");
+ NS_TEST_ASSERT_MSG_EQ (foundTestObject, 0,
+ "Unexpectedly able to GetObject<TestObject> on an AlternateTestObject");
+
+ return false;
+}
+
+class NamesTestSuite : public TestSuite
+{
+public:
+ NamesTestSuite ();
+};
+
+NamesTestSuite::NamesTestSuite ()
+ : TestSuite ("object-name-service", UNIT)
+{
+ AddTestCase (new BasicAddTestCase);
+ AddTestCase (new StringContextAddTestCase);
+ AddTestCase (new FullyQualifiedAddTestCase);
+ AddTestCase (new RelativeAddTestCase);
+ AddTestCase (new BasicRenameTestCase);
+ AddTestCase (new StringContextRenameTestCase);
+ AddTestCase (new FullyQualifiedRenameTestCase);
+ AddTestCase (new RelativeRenameTestCase);
+ AddTestCase (new FindPathTestCase);
+ AddTestCase (new BasicFindTestCase);
+ AddTestCase (new StringContextFindTestCase);
+ AddTestCase (new FullyQualifiedFindTestCase);
+ AddTestCase (new RelativeFindTestCase);
+ AddTestCase (new AlternateFindTestCase);
+}
+
+NamesTestSuite namesTestSuite;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/rng-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,434 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <math.h>
+#include <gsl/gsl_cdf.h>
+#include <gsl/gsl_histogram.h>
+#include <time.h>
+#include <fstream>
+
+#include "test.h"
+#include "random-variable.h"
+
+using namespace ns3;
+
+void
+FillHistoRangeUniformly (double *array, uint32_t n, double start, double end)
+{
+ double increment = (end - start) / (n - 1.);
+ double d = start;
+
+ for (uint32_t i = 0; i < n; ++i)
+ {
+ array[i] = d;
+ d += increment;
+ }
+}
+
+// ===========================================================================
+// Test case for uniform distribution random number generator
+// ===========================================================================
+class RngUniformTestCase : public TestCase
+{
+public:
+ static const uint32_t N_RUNS = 5;
+ static const uint32_t N_BINS = 50;
+ static const uint32_t N_MEASUREMENTS = 1000000;
+
+ RngUniformTestCase ();
+ virtual ~RngUniformTestCase ();
+
+ double ChiSquaredTest (UniformVariable &u);
+
+private:
+ virtual bool DoRun (void);
+};
+
+RngUniformTestCase::RngUniformTestCase ()
+ : TestCase ("Uniform Random Number Generator")
+{
+}
+
+RngUniformTestCase::~RngUniformTestCase ()
+{
+}
+
+double
+RngUniformTestCase::ChiSquaredTest (UniformVariable &u)
+{
+ gsl_histogram * h = gsl_histogram_alloc (N_BINS);
+ gsl_histogram_set_ranges_uniform (h, 0., 1.);
+
+ for (uint32_t i = 0; i < N_MEASUREMENTS; ++i)
+ {
+ gsl_histogram_increment (h, u.GetValue ());
+ }
+
+ double tmp[N_BINS];
+
+ double expected = ((double)N_MEASUREMENTS / (double)N_BINS);
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ tmp[i] = gsl_histogram_get (h, i);
+ tmp[i] -= expected;
+ tmp[i] *= tmp[i];
+ tmp[i] /= expected;
+ }
+
+ gsl_histogram_free (h);
+
+ double chiSquared = 0;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ chiSquared += tmp[i];
+ }
+
+ return chiSquared;
+}
+
+bool
+RngUniformTestCase::DoRun (void)
+{
+ SeedManager::SetSeed (time (0));
+
+ double sum = 0.;
+ double maxStatistic = gsl_cdf_chisq_Qinv (0.05, N_BINS);
+
+ for (uint32_t i = 0; i < N_RUNS; ++i)
+ {
+ UniformVariable u;
+ double result = ChiSquaredTest (u);
+ sum += result;
+ }
+
+ sum /= (double)N_RUNS;
+
+ NS_TEST_ASSERT_MSG_LT (sum, maxStatistic, "Chi-squared statistic out of range");
+ return false;
+}
+
+// ===========================================================================
+// Test case for normal distribution random number generator
+// ===========================================================================
+class RngNormalTestCase : public TestCase
+{
+public:
+ static const uint32_t N_RUNS = 5;
+ static const uint32_t N_BINS = 50;
+ static const uint32_t N_MEASUREMENTS = 1000000;
+
+ RngNormalTestCase ();
+ virtual ~RngNormalTestCase ();
+
+ double ChiSquaredTest (NormalVariable &n);
+
+private:
+ virtual bool DoRun (void);
+};
+
+RngNormalTestCase::RngNormalTestCase ()
+ : TestCase ("Normal Random Number Generator")
+{
+}
+
+RngNormalTestCase::~RngNormalTestCase ()
+{
+}
+
+double
+RngNormalTestCase::ChiSquaredTest (NormalVariable &n)
+{
+ gsl_histogram * h = gsl_histogram_alloc (N_BINS);
+
+ double range[N_BINS + 1];
+ FillHistoRangeUniformly (range, N_BINS + 1, -4., 4.);
+ range[0] = -std::numeric_limits<double>::max ();
+ range[N_BINS] = std::numeric_limits<double>::max ();
+
+ gsl_histogram_set_ranges (h, range, N_BINS + 1);
+
+ double expected[N_BINS];
+
+ double sigma = 1.;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ expected[i] = gsl_cdf_gaussian_P (range[i + 1], sigma) - gsl_cdf_gaussian_P (range[i], sigma);
+ expected[i] *= N_MEASUREMENTS;
+ }
+
+ for (uint32_t i = 0; i < N_MEASUREMENTS; ++i)
+ {
+ gsl_histogram_increment (h, n.GetValue ());
+ }
+
+ double tmp[N_BINS];
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ tmp[i] = gsl_histogram_get (h, i);
+ tmp[i] -= expected[i];
+ tmp[i] *= tmp[i];
+ tmp[i] /= expected[i];
+ }
+
+ gsl_histogram_free (h);
+
+ double chiSquared = 0;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ chiSquared += tmp[i];
+ }
+
+ return chiSquared;
+}
+
+bool
+RngNormalTestCase::DoRun (void)
+{
+ SeedManager::SetSeed (time (0));
+
+ double sum = 0.;
+ double maxStatistic = gsl_cdf_chisq_Qinv (0.05, N_BINS);
+
+ for (uint32_t i = 0; i < N_RUNS; ++i)
+ {
+ NormalVariable n;
+ double result = ChiSquaredTest (n);
+ sum += result;
+ }
+
+ sum /= (double)N_RUNS;
+
+ NS_TEST_ASSERT_MSG_LT (sum, maxStatistic, "Chi-squared statistic out of range");
+ return false;
+}
+
+// ===========================================================================
+// Test case for exponential distribution random number generator
+// ===========================================================================
+class RngExponentialTestCase : public TestCase
+{
+public:
+ static const uint32_t N_RUNS = 5;
+ static const uint32_t N_BINS = 50;
+ static const uint32_t N_MEASUREMENTS = 1000000;
+
+ RngExponentialTestCase ();
+ virtual ~RngExponentialTestCase ();
+
+ double ChiSquaredTest (ExponentialVariable &n);
+
+private:
+ virtual bool DoRun (void);
+};
+
+RngExponentialTestCase::RngExponentialTestCase ()
+ : TestCase ("Exponential Random Number Generator")
+{
+}
+
+RngExponentialTestCase::~RngExponentialTestCase ()
+{
+}
+
+double
+RngExponentialTestCase::ChiSquaredTest (ExponentialVariable &e)
+{
+ gsl_histogram * h = gsl_histogram_alloc (N_BINS);
+
+ double range[N_BINS + 1];
+ FillHistoRangeUniformly (range, N_BINS + 1, 0., 10.);
+ range[N_BINS] = std::numeric_limits<double>::max ();
+
+ gsl_histogram_set_ranges (h, range, N_BINS + 1);
+
+ double expected[N_BINS];
+
+ double mu = 1.;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ expected[i] = gsl_cdf_exponential_P (range[i + 1], mu) - gsl_cdf_exponential_P (range[i], mu);
+ expected[i] *= N_MEASUREMENTS;
+ }
+
+ for (uint32_t i = 0; i < N_MEASUREMENTS; ++i)
+ {
+ gsl_histogram_increment (h, e.GetValue ());
+ }
+
+ double tmp[N_BINS];
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ tmp[i] = gsl_histogram_get (h, i);
+ tmp[i] -= expected[i];
+ tmp[i] *= tmp[i];
+ tmp[i] /= expected[i];
+ }
+
+ gsl_histogram_free (h);
+
+ double chiSquared = 0;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ chiSquared += tmp[i];
+ }
+
+ return chiSquared;
+}
+
+bool
+RngExponentialTestCase::DoRun (void)
+{
+ SeedManager::SetSeed (time (0));
+
+ double sum = 0.;
+ double maxStatistic = gsl_cdf_chisq_Qinv (0.05, N_BINS);
+
+ for (uint32_t i = 0; i < N_RUNS; ++i)
+ {
+ ExponentialVariable e;
+ double result = ChiSquaredTest (e);
+ sum += result;
+ }
+
+ sum /= (double)N_RUNS;
+
+ NS_TEST_ASSERT_MSG_LT (sum, maxStatistic, "Chi-squared statistic out of range");
+ return false;
+}
+
+// ===========================================================================
+// Test case for pareto distribution random number generator
+// ===========================================================================
+class RngParetoTestCase : public TestCase
+{
+public:
+ static const uint32_t N_RUNS = 5;
+ static const uint32_t N_BINS = 50;
+ static const uint32_t N_MEASUREMENTS = 1000000;
+
+ RngParetoTestCase ();
+ virtual ~RngParetoTestCase ();
+
+ double ChiSquaredTest (ParetoVariable &p);
+
+private:
+ virtual bool DoRun (void);
+};
+
+RngParetoTestCase::RngParetoTestCase ()
+ : TestCase ("Pareto Random Number Generator")
+{
+}
+
+RngParetoTestCase::~RngParetoTestCase ()
+{
+}
+
+double
+RngParetoTestCase::ChiSquaredTest (ParetoVariable &p)
+{
+ gsl_histogram * h = gsl_histogram_alloc (N_BINS);
+
+ double range[N_BINS + 1];
+ FillHistoRangeUniformly (range, N_BINS + 1, 1., 10.);
+ range[N_BINS] = std::numeric_limits<double>::max ();
+
+ gsl_histogram_set_ranges (h, range, N_BINS + 1);
+
+ double expected[N_BINS];
+
+ double a = 1.5;
+ double b = 0.33333333;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ expected[i] = gsl_cdf_pareto_P (range[i + 1], a, b) - gsl_cdf_pareto_P (range[i], a, b);
+ expected[i] *= N_MEASUREMENTS;
+ }
+
+ for (uint32_t i = 0; i < N_MEASUREMENTS; ++i)
+ {
+ gsl_histogram_increment (h, p.GetValue ());
+ }
+
+ double tmp[N_BINS];
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ tmp[i] = gsl_histogram_get (h, i);
+ tmp[i] -= expected[i];
+ tmp[i] *= tmp[i];
+ tmp[i] /= expected[i];
+ }
+
+ gsl_histogram_free (h);
+
+ double chiSquared = 0;
+
+ for (uint32_t i = 0; i < N_BINS; ++i)
+ {
+ chiSquared += tmp[i];
+ }
+
+ return chiSquared;
+}
+
+bool
+RngParetoTestCase::DoRun (void)
+{
+ SeedManager::SetSeed (time (0));
+
+ double sum = 0.;
+ double maxStatistic = gsl_cdf_chisq_Qinv (0.05, N_BINS);
+
+ for (uint32_t i = 0; i < N_RUNS; ++i)
+ {
+ ParetoVariable e;
+ double result = ChiSquaredTest (e);
+ sum += result;
+ }
+
+ sum /= (double)N_RUNS;
+
+ NS_TEST_ASSERT_MSG_LT (sum, maxStatistic, "Chi-squared statistic out of range");
+ return false;
+}
+
+class RngTestSuite : public TestSuite
+{
+public:
+ RngTestSuite ();
+};
+
+RngTestSuite::RngTestSuite ()
+ : TestSuite ("random-number-generators", UNIT)
+{
+ AddTestCase (new RngUniformTestCase);
+ AddTestCase (new RngNormalTestCase);
+ AddTestCase (new RngExponentialTestCase);
+ AddTestCase (new RngParetoTestCase);
+}
+
+RngTestSuite rngTestSuite;
--- a/src/core/test.cc Thu Sep 10 11:41:33 2009 +0100
+++ b/src/core/test.cc Sat Sep 12 19:44:17 2009 -0700
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2005 INRIA
+ * Copyright (c) 2009 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -14,14 +14,530 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
*/
#include "test.h"
+#include "abort.h"
+#include <math.h>
+
+namespace ns3 {
+
+bool
+TestDoubleIsEqual (const double x1, const double x2, const double epsilon)
+{
+ int exponent;
+ double delta, difference;
+
+ //
+ // Find exponent of largest absolute value
+ //
+ {
+ double max = (fabs (x1) > fabs (x2)) ? x1 : x2;
+ frexp (max, &exponent);
+ }
+
+ //
+ // Form a neighborhood of size 2 * delta
+ //
+ delta = ldexp (epsilon, exponent);
+ difference = x1 - x2;
+
+ if (difference > delta || difference < -delta)
+ {
+ return false;
+ }
+ return true;
+}
+
+
+TestCase::TestCase (std::string name)
+ : m_name (name), m_verbose (false), m_basedir ("invalid"), m_ofs (0), m_error (false)
+{
+}
+
+TestCase::~TestCase ()
+{
+}
+
+void
+TestCase::ReportStart (void)
+{
+ DoReportStart ();
+}
+
+void
+TestCase::ReportSuccess (void)
+{
+ DoReportSuccess ();
+}
+
+void
+TestCase::ReportFailure (
+ std::string cond,
+ std::string actual,
+ std::string limit,
+ std::string message,
+ std::string file,
+ int32_t line)
+{
+ DoReportFailure (cond, actual, limit, message, file, line);
+}
+
+void
+TestCase::ReportEnd (void)
+{
+ DoReportStart ();
+}
+
+bool
+TestCase::Run (void)
+{
+ DoReportStart ();
+ DoSetup ();
+ m_error |= DoRun ();
+ DoTeardown ();
+ if (m_error == false)
+ {
+ DoReportSuccess ();
+ }
+ DoReportEnd ();
+ return m_error;
+}
+
+void
+TestCase::SetVerbose (bool verbose)
+{
+ m_verbose = verbose;
+}
+
+void
+TestCase::SetName (std::string name)
+{
+ m_name = name;
+}
+
+std::string
+TestCase::GetName (void)
+{
+ return m_name;
+}
+
+void
+TestCase::SetBaseDir (std::string basedir)
+{
+ m_basedir = basedir;
+}
+
+std::string
+TestCase::GetBaseDir (void)
+{
+ return m_basedir;
+}
+
+std::string
+TestCase::GetSourceDir (std::string file)
+{
+ std::string::size_type relPathBegin = file.find_first_of ("/");
+ NS_ABORT_MSG_IF (relPathBegin == std::string::npos, "TestCase::GetSrouceDir(): Internal Error");
+ std::string::size_type relPathEnd = file.find_last_of ("/");
+ NS_ABORT_MSG_IF (relPathEnd == std::string::npos, "TestCase::GetSrouceDir(): Internal Error");
+
+ return GetBaseDir () + file.substr (relPathBegin, relPathEnd + 1 - relPathBegin);
+}
+
+void
+TestCase::SetStream (std::ofstream *ofs)
+{
+ m_ofs = ofs;
+}
+
+std::ofstream *
+TestCase::GetStream (void)
+{
+ return m_ofs;
+}
+
+void
+TestCase::SetErrorStatus (bool error)
+{
+ m_error = error;
+}
+
+bool
+TestCase::GetErrorStatus (void)
+{
+ return m_error;
+}
+
+
+void
+TestCase::DoReportStart (void)
+{
+ m_startTime = times (&m_startTimes);
+
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << " <TestCase>" << std::endl;
+ *m_ofs << " <CaseName>" << GetName () << "</CaseName>" << std::endl;
+}
+
+void
+TestCase::DoReportSuccess (void)
+{
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << " <CaseResult>PASS</CaseResult>" << std::endl;
+}
+
+void
+TestCase::DoReportFailure (
+ std::string cond,
+ std::string actual,
+ std::string limit,
+ std::string message,
+ std::string file,
+ int32_t line)
+{
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << " <CaseResult>FAIL</CaseResult>" << std::endl;
+ *m_ofs << " <CaseCondition>" << cond << "</CaseCondition>" << std::endl;
+ *m_ofs << " <CaseActual>" << actual << "</CaseActual>" << std::endl;
+ *m_ofs << " <CaseLimit>" << limit << "</CaseLimit>" << std::endl;
+ *m_ofs << " <CaseMessage>" << message << "</CaseMessage>" << std::endl;
+ *m_ofs << " <CaseFile>" << file << "</CaseFile>" << std::endl;
+ *m_ofs << " <CaseLine>" << line << "</CaseLine>" << std::endl;
+ m_error |= true;
+}
+
+void
+TestCase::DoReportEnd (void)
+{
+ static long ticksPerSecond = sysconf (_SC_CLK_TCK);
+
+ if (m_ofs == 0)
+ {
+ return;
+ }
+
+ struct tms endTimes;
+ clock_t endTime = times (&endTimes);
+
+ clock_t elapsed = endTime - m_startTime;
+ clock_t elapsedUsr = endTimes.tms_utime - m_startTimes.tms_utime;
+ clock_t elapsedSys = endTimes.tms_stime - m_startTimes.tms_stime;
+
+ (*m_ofs).precision (2);
+ *m_ofs << std::fixed;
+
+ *m_ofs << " <CaseTime>" << "real " << static_cast<double> (elapsed) / ticksPerSecond
+ << " user " << static_cast<double> (elapsedUsr) / ticksPerSecond
+ << " system " << static_cast<double> (elapsedSys) / ticksPerSecond
+ << "</CaseTime>" << std::endl;
+
+ *m_ofs << " </TestCase>" << std::endl;
+}
+
+void
+TestCase::DoSetup (void)
+{
+}
+
+void
+TestCase::DoTeardown (void)
+{
+}
+
+TestSuite::TestSuite (std::string name, TestType type)
+ : m_name (name), m_verbose (false), m_basedir ("invalid"), m_ofs (0), m_type (type)
+{
+ TestRunner::AddTestSuite (this);
+}
+
+TestSuite::~TestSuite ()
+{
+ for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
+ {
+ delete *i;
+ *i = 0;
+ }
+
+ m_tests.erase (m_tests.begin (), m_tests.end ());
+}
+
+void
+TestSuite::ReportStart (void)
+{
+ DoReportStart ();
+}
+
+void
+TestSuite::ReportSuccess (void)
+{
+ DoReportSuccess ();
+}
+
+void
+TestSuite::ReportFailure (void)
+{
+ DoReportFailure ();
+}
+
+void
+TestSuite::ReportEnd (void)
+{
+ DoReportEnd ();
+}
+
+bool
+TestSuite::Run (void)
+{
+ DoReportStart ();
+ DoSetup ();
+ bool error = DoRun ();
+ DoTeardown ();
+ if (error == false)
+ {
+ DoReportSuccess ();
+ }
+ else
+ {
+ DoReportFailure ();
+ }
+
+ DoReportEnd ();
+ return error;
+}
+
+uint32_t
+TestSuite::AddTestCase (TestCase *testCase)
+{
+ uint32_t index = m_tests.size ();
+ m_tests.push_back (testCase);
+ return index;
+}
+
+uint32_t
+TestSuite::GetNTestCases (void)
+{
+ return m_tests.size ();
+}
+
+TestCase *
+TestSuite::GetTestCase (uint32_t n)
+{
+ return m_tests[n];
+}
+
+TestSuite::TestType
+TestSuite::GetTestType (void)
+{
+ return m_type;
+}
+
+void
+TestSuite::SetVerbose (bool verbose)
+{
+ m_verbose = verbose;
+}
+
+void
+TestSuite::SetName (std::string name)
+{
+ m_name = name;
+}
+
+std::string
+TestSuite::GetName (void)
+{
+ return m_name;
+}
+
+void
+TestSuite::SetBaseDir (std::string basedir)
+{
+ m_basedir = basedir;
+}
+
+std::string
+TestSuite::GetBaseDir (void)
+{
+ return m_basedir;
+}
+
+void
+TestSuite::SetStream (std::ofstream *ofs)
+{
+ m_ofs = ofs;
+}
+
+void
+TestSuite::DoReportStart (void)
+{
+ m_startTime = times (&m_startTimes);
+
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << "<TestSuite>" << std::endl;
+ *m_ofs << " <SuiteName>" << GetName () << "</SuiteName>" << std::endl;
+}
+
+void
+TestSuite::DoReportFailure (void)
+{
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << " <SuiteResult>FAIL</SuiteResult>" << std::endl;
+}
+
+void
+TestSuite::DoReportSuccess (void)
+{
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ *m_ofs << " <SuiteResult>PASS</SuiteResult>" << std::endl;
+}
+
+void
+TestSuite::DoReportEnd (void)
+{
+ static long ticksPerSecond = sysconf (_SC_CLK_TCK);
+
+ if (m_ofs == 0)
+ {
+ return;
+ }
+ struct tms endTimes;
+ clock_t endTime = times (&endTimes);
+
+ clock_t elapsed = endTime - m_startTime;
+ clock_t elapsedUsr = endTimes.tms_utime - m_startTimes.tms_utime;
+ clock_t elapsedSys = endTimes.tms_stime - m_startTimes.tms_stime;
+
+ (*m_ofs).precision (2);
+ *m_ofs << std::fixed;
+
+ *m_ofs << " <SuiteTime>" << "real " << static_cast<double> (elapsed) / ticksPerSecond
+ << " user " << static_cast<double> (elapsedUsr) / ticksPerSecond
+ << " system " << static_cast<double> (elapsedSys) / ticksPerSecond
+ << "</SuiteTime>" << std::endl;
+
+ *m_ofs << "</TestSuite>" << std::endl;
+}
+
+void
+TestSuite::DoSetup (void)
+{
+}
+
+bool
+TestSuite::DoRun (void)
+{
+ for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
+ {
+ (*i)->SetVerbose (m_verbose);
+ (*i)->SetBaseDir (m_basedir);
+ (*i)->SetStream (m_ofs);
+ bool err = (*i)->Run ();
+ if (err)
+ {
+ return err;
+ }
+ }
+
+ return false;
+}
+
+void
+TestSuite::DoTeardown (void)
+{
+}
+
+class TestRunnerImpl
+{
+public:
+ uint32_t AddTestSuite (TestSuite *testSuite);
+ uint32_t GetNTestSuites (void);
+ TestSuite *GetTestSuite (uint32_t n);
+ bool RunTestSuite (uint32_t n);
+
+ static TestRunnerImpl *Instance (void);
+private:
+ TestRunnerImpl ();
+ ~TestRunnerImpl ();
+
+ typedef std::vector<TestSuite *> TestSuiteVector_t;
+ TestSuiteVector_t m_suites;
+};
+
+TestRunnerImpl::TestRunnerImpl ()
+{
+}
+
+TestRunnerImpl::~TestRunnerImpl ()
+{
+}
+
+TestRunnerImpl *
+TestRunnerImpl::Instance (void)
+{
+ static TestRunnerImpl runner;
+ return &runner;
+}
+
+uint32_t
+TestRunnerImpl::AddTestSuite (TestSuite *testSuite)
+{
+ uint32_t index = m_suites.size ();
+ m_suites.push_back (testSuite);
+ return index;
+}
+
+uint32_t
+TestRunnerImpl::GetNTestSuites (void)
+{
+ return m_suites.size ();
+}
+
+TestSuite *
+TestRunnerImpl::GetTestSuite (uint32_t n)
+{
+ return m_suites[n];
+}
+
+uint32_t
+TestRunner::AddTestSuite (TestSuite *testSuite)
+{
+ return TestRunnerImpl::Instance ()->AddTestSuite (testSuite);
+}
+
+uint32_t
+TestRunner::GetNTestSuites (void)
+{
+ return TestRunnerImpl::Instance ()->GetNTestSuites ();
+}
+
+TestSuite *
+TestRunner::GetTestSuite (uint32_t n)
+{
+ return TestRunnerImpl::Instance ()->GetTestSuite (n);
+}
+
+}; // namespace ns3
#ifdef RUN_SELF_TESTS
-#include <iostream>
namespace ns3 {
@@ -66,7 +582,6 @@
}
}
-
std::ostream &
TestManager::Failure (void)
{
--- a/src/core/test.h Thu Sep 10 11:41:33 2009 +0100
+++ b/src/core/test.h Sat Sep 12 19:44:17 2009 -0700
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2005 INRIA
+ * Copyright (c) 2009 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -14,18 +14,984 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
*/
#ifndef TEST_H
#define TEST_H
-#include <list>
+#include <iostream>
+#include <fstream>
+#include <sstream>
#include <string>
-#include <utility>
-#include <ostream>
+#include <vector>
+#include <list>
+#include <sys/times.h>
+//
+// Note on below macros:
+//
+// When multiple statements are used in a macro, they should be bound together
+// in a loop syntactically, so the macro can appear safely inside if clauses
+// or other places that expect a single statement or a statement block. The
+// "strange" do while construct is a generally expected best practice for
+// defining a robust macro.
+//
+
+/**
+ * \brief Convenience macro to extract the source directory of the current
+ * source file.
+ *
+ * \see TestCase::GetSourceDir
+ */
+#define NS_TEST_SOURCEDIR \
+ TestCase::GetSourceDir (__FILE__)
+
+// ===========================================================================
+// Test for equality (generic version)
+// ===========================================================================
+
+/**
+ * \internal
+ */
+#define NS_TEST_ASSERT_MSG_EQ_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) == (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) EQ " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ return true; \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual and expected (limit) value are equal and report
+ * and abort if not.
+ *
+ * Check to see if the expected (limit) value is equal to the actual value found
+ * in a test case. If the two values are equal nothing happens, but if the
+ * comparison fails, an error is reported in a consistent way and the execution
+ * of the current test case is aborted.
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_ASSERT_MSG_EQ (result, true,
+ * "cannot open file " << filename << " in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param msg Message that is output if the test does not pass.
+ *
+ * \warning Do not use this macro if you are comparing floating point numbers
+ * (float or double) as it is unlikely to do what you expect. Use
+ * NS_TEST_ASSERT_MSG_EQ_TOL instead.
+ */
+#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg) \
+ NS_TEST_ASSERT_MSG_EQ_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+/**
+ * \internal
+ *
+ * Required to avoid use of return statement which allows use in methods
+ * (esp. callbacks) returning void.
+ */
+#define NS_TEST_EXPECT_MSG_EQ_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) == (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) EQ " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual and expected (limit) value are equal and report
+ * if not.
+ *
+ * Check to see if the expected (lmit) value is equal to the actual value found
+ * in a test case. If the two values are equal nothing happens, but if the
+ * comparison fails, an error is reported in a consistent way. EXPECT* macros
+ * do not return if an error is detected.
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_EXPECT_MSG_EQUAL (result, true,
+ * "cannot open file " << filename << " in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param msg Message that is output if the test does not pass.
+ *
+ * \warning Do not use this macro if you are comparing floating point numbers
+ * (float or double) as it is unlikely to do what you expect. Use
+ * NS_TEST_EXPECT_MSG_EQ_TOL instead.
+ */
+#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg) \
+ NS_TEST_EXPECT_MSG_EQ_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+// ===========================================================================
+// Test for equality with a provided tolerance (use for floating point
+// comparisons -- both float and double)
+// ===========================================================================
+
+/**
+ * \internal
+ */
+#define NS_TEST_ASSERT_MSG_EQ_TOL_INTERNAL(actual, limit, tol, msg, file, line) \
+ do { \
+ if ((actual) > (limit) + (tol) || (actual) < (limit) - (tol)) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit << " +- " << tol; \
+ std::ostringstream condStream; \
+ condStream << #actual << " (actual) LT " << #limit << " (limit) + " << #tol << " (tol) AND " << \
+ #actual << " (actual) GT " << #limit << " (limit) - " << #tol << " (tol)"; \
+ ReportFailure (condStream.str (), actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ return true; \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that actual and expected (limit) values are equal to plus or minus
+ * some tolerance and report and abort if not.
+ *
+ * Check to see if the expected (limit) value is equal to the actual value found
+ * in a test case to some tolerance. This is not the same thing as asking if
+ * two floating point are equal to within some epsilon, but is useful for that
+ * case. This assertion is geared toward more of a measurement problem. Consider
+ * measuring a physical rod of some kind that you have ordered. You need to
+ * determine if it is "good." You won't measure the rod to an arbitrary precision
+ * of sixteen significant figures, you will measure the rod to determine if its
+ * length is within the tolerances you provided. For example, 12.00 inches plus
+ * or minus .005 inch may be just fine.
+ *
+ * In ns-3, you might want to measure a signal to noise ratio and check to see
+ * if the answer is what you expect. If you naively measure (double)1128.93 and
+ * compare this number with a constant 1128.93 you are almost certainly going to
+ * have your test fail because of floating point rounding errors. We provide a
+ * floating point comparison function ns3::TestDoubleIsEqual() but you will
+ * probably quickly find that is not what you want either. It may turn out to
+ * be the case that when you measured an snr that printed as 1128.93, what was
+ * actually measured was something more like 1128.9287653857625442 for example.
+ * Given that the double epsilon is on the order of 0.0000000000000009, you would
+ * need to provide sixteen significant figures of expected value for this kind of
+ * test to pass even with a typical test for floating point "approximate equality."
+ * That is clearly not required or desired. You really want to be able to provide
+ * 1128.93 along with a tolerance just like you provided 12 inches +- 0.005 inch
+ * above.
+ *
+ * This assertion is designed for real measurements by taking into account
+ * measurement tolerances. By doing so it also automatically compensates for
+ * floating point rounding errors. If you really want to check floating point
+ * equality down to the numeric_limits<double>::epsilon () range, consider using
+ * ns3::TestDoubleIsEqual().
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_ASSERT_MSG_EQ_TOL (snr, 1128.93, 0.005, "wrong snr (" << snr << ") in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param tol Tolerance of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_ASSERT_MSG_EQ_TOL(actual, limit, tol, msg) \
+ NS_TEST_ASSERT_MSG_EQ_TOL_INTERNAL (actual, limit, tol, msg, __FILE__, __LINE__)
+
+/**
+ * \internal
+ *
+ * Required to avoid use of return statement which allows use in methods
+ * (esp. callbacks) returning void.
+ */
+#define NS_TEST_EXPECT_MSG_EQ_TOL_INTERNAL(actual, limit, tol, msg, file, line) \
+ do { \
+ if ((actual) > (limit) + (tol) || (actual) < (limit) - (tol)) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit << " +- " << tol; \
+ std::ostringstream condStream; \
+ condStream << #actual << " (actual) LT " << #limit << " (limit) + " << #tol << " (tol) AND " << \
+ #actual << " (actual) GT " << #limit << " (limit) - " << #tol << " (tol)"; \
+ ReportFailure (condStream.str (), actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that actual and expected (limit) values are equal to plus or minus
+ * some tolerance and report if not.
+ *
+ * Check to see if the expected (limit) value is equal to the actual value found
+ * in a test case to some tolerance. This is not the same thing as asking if
+ * two floating point are equal to within some epsilon, but is useful for that
+ * case. This assertion is geared toward more of a measurement problem. Consider
+ * measuring a physical rod of some kind that you have ordered. You need to
+ * determine if it is "good." You won't measure the rod to an arbitrary precision
+ * of sixteen significant figures, you will measure the rod to determine if its
+ * length is within the tolerances you provided. For example, 12.00 inches plus
+ * or minus .005 inch may be just fine.
+ *
+ * In ns-3, you might want to measure a signal to noise ratio and check to see
+ * if the answer is what you expect. If you naively measure (double)1128.93 and
+ * compare this number with a constant 1128.93 you are almost certainly going to
+ * have your test fail because of floating point rounding errors. We provide a
+ * floating point comparison function ns3::TestDoubleIsEqual() but you will
+ * probably quickly find that is not what you want either. It may turn out to
+ * be the case that when you measured an snr that printed as 1128.93, what was
+ * actually measured was something more like 1128.9287653857625442 for example.
+ * Given that the double epsilon is on the order of 0.0000000000000009, you would
+ * need to provide sixteen significant figures of expected value for this kind of
+ * test to pass even with a typical test for floating point "approximate equality."
+ * That is clearly not required or desired. You really want to be able to provide
+ * 1128.93 along with a tolerance just like you provided 12 inches +- 0.005 inch
+ * above.
+ *
+ * This assertion is designed for real measurements by taking into account
+ * measurement tolerances. By doing so it also automatically compensates for
+ * floating point rounding errors. If you really want to check floating point
+ * equality down to the numeric_limits<double>::epsilon () range, consider using
+ * ns3::TestDoubleIsEqual().
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_EXPECT_MSG_EQ_TOL (snr, 1128.93, 0.005, "wrong snr (" << snr << ") in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param tol Tolerance of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_EXPECT_MSG_EQ_TOL(actual, limit, tol, msg) \
+ NS_TEST_EXPECT_MSG_EQ_TOL_INTERNAL (actual, limit, tol, msg, __FILE__, __LINE__)
+
+// ===========================================================================
+// Test for inequality
+// ===========================================================================
+
+/**
+ * \internal
+ */
+#define NS_TEST_ASSERT_MSG_NE_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) != (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) NE " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ return true; \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual and expected (limit) value are equal and report
+ * and abort if not.
+ *
+ * Check to see if the expected (limit) value is not equal to the actual value
+ * found in a test case. If the two values are equal nothing happens, but if
+ * the comparison fails, an error is reported in a consistent way and the
+ * execution of the current test case is aborted.
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_ASSERT_MSG_NE (result, false,
+ * "cannot open file " << filename << " in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param msg Message that is output if the test does not pass.
+ *
+ * \warning Do not use this macro if you are comparing floating point numbers
+ * (float or double). Use NS_TEST_ASSERT_MSG_FLNE instead.
+ */
+#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg) \
+ NS_TEST_ASSERT_MSG_NE_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+/**
+ * \internal
+ *
+ * Required to avoid use of return statement which allows use in methods
+ * (callbacks) returning void.
+ */
+#define NS_TEST_EXPECT_MSG_NE_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) != (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) NE " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual and expected (limit) value are equal and report
+ * if not.
+ *
+ * Check to see if the expected (limit) value is equal to the actual value
+ * found in a test case. If the two values are equal nothing happens, but if
+ * the comparison fails, an error is reported in a consistent way. EXPECT*
+ * macros do not return if an error is detected.
+ *
+ * The message is interpreted as a stream, for example:
+ *
+ * \code
+ * NS_TEST_EXPECT_MSG_EQUAL (result, true,
+ * "cannot open file " << filename << " in test");
+ * \endcode
+ *
+ * is legal.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the expected value of the test.
+ * \param msg Message that is output if the test does not pass.
+ *
+ * \warning Do not use this macro if you are comparing floating point numbers
+ * (float or double). Use NS_TEST_EXPECT_MSG_FLNE instead.
+ */
+#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg) \
+ NS_TEST_EXPECT_MSG_NE_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+// ===========================================================================
+// Test for less than relation
+// ===========================================================================
+
+/**
+ * \internal
+ */
+#define NS_TEST_ASSERT_MSG_LT_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) < (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) LT " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ return true; \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual value is less than a limit and report and abort
+ * if not.
+ *
+ * Check to see if the actual value found in a test case is less than the
+ * limit value. If the actual value is lesser nothing happens, but if the
+ * check fails, an error is reported in a consistent way and the execution
+ * of the current test case is aborted.
+ *
+ * The message is interpreted as a stream.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the limit value of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_ASSERT_MSG_LT(actual, limit, msg) \
+ NS_TEST_ASSERT_MSG_LT_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+/**
+ * \internal
+ *
+ * Required to avoid use of return statement which allows use in methods
+ * (callbacks) returning void.
+ */
+#define NS_TEST_EXPECT_MSG_LT_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) < (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) LT " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual value is less than a limit and report if not.
+ *
+ * Check to see if the actual value found in a test case is less than the
+ * limit value. If the actual value is lesser nothing happens, but if the
+ * check fails, an error is reported in a consistent way. EXPECT* macros do
+ * not return if an error is detected.
+ *
+ * The message is interpreted as a stream.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the limit value of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg) \
+ NS_TEST_EXPECT_MSG_LT_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+// ===========================================================================
+// Test for greater than relation
+// ===========================================================================
+
+/**
+ * \internal
+ */
+#define NS_TEST_ASSERT_MSG_GT_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) > (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) GT " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ return true; \
+ } \
+ } while (false)
+/**
+ * \brief Test that an actual value is greater than a limit and report and abort
+ * if not.
+ *
+ * Check to see if the actaul value found in a test case is greater than the
+ * limit value. If the actual value is greater nothing happens, but if the
+ * check fails, an error is reported in a consistent way and the execution
+ * of the current test case is aborted.
+ *
+ * The message is interpreted as a stream.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the limit value of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_ASSERT_MSG_GT(actual, limit, msg) \
+ NS_TEST_ASSERT_MSG_GT_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+/**
+ * \internal
+ *
+ * Required to avoid use of return statement which allows use in methods
+ * (callbacks) returning void.
+ */
+#define NS_TEST_EXPECT_MSG_GT_INTERNAL(actual, limit, msg, file, line) \
+ do { \
+ if (!((actual) > (limit))) \
+ { \
+ std::ostringstream msgStream; \
+ msgStream << msg; \
+ std::ostringstream actualStream; \
+ actualStream << actual; \
+ std::ostringstream limitStream; \
+ limitStream << limit; \
+ ReportFailure (std::string (#actual) + " (actual) GT " + std::string (#limit) + " (limit)", \
+ actualStream.str (), limitStream.str (), msgStream.str (), file, line); \
+ } \
+ } while (false)
+
+/**
+ * \brief Test that an actual value is greater than a limit and report if not.
+ *
+ * Check to see if the actual value found in a test case is greater than the
+ * limit value. If the actual value is greater nothing happens, but if the
+ * check fails, an error is reported in a consistent way. EXPECT* macros do
+ * not return if an error is detected.
+ *
+ * The message is interpreted as a stream.
+ *
+ * \param actual Expression for the actual value found during the test.
+ * \param limit Expression for the limit value of the test.
+ * \param msg Message that is output if the test does not pass.
+ */
+#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg) \
+ NS_TEST_EXPECT_MSG_GT_INTERNAL (actual, limit, msg, __FILE__, __LINE__)
+
+namespace ns3 {
+
+/**
+ * \brief Compare two double precision floating point numbers and declare them
+ * equal if they are within some epsilon of each other.
+ *
+ * Approximate comparison of floating point numbers near equality is trickier
+ * than one may expect and is well-discussed in the literature. Basic
+ * strategies revolve around a suggestion by Knuth to compare the floating
+ * point numbers as binary integers, supplying a maximum difference between
+ * them . This max difference is specified in Units in the Last Place (ulps)
+ * or a floating point epsilon.
+ *
+ * This routine is based on the GNU Scientific Library function gsl_fcmp.
+ *
+ * \param a The first of double precision floating point numbers to compare
+ * \param a The second of double precision floating point numbers to compare
+ * \param epsilon The second of double precision floating point numberss to compare
+ * \returns Returns true if the doubles are equal to a precision defined by epsilon
+ */
+ bool TestDoubleIsEqual (const double a, const double b, const double epsilon = std::numeric_limits<double>::epsilon ());
+
+/**
+ * \brief A single test case.
+ */
+class TestCase
+{
+public:
+ TestCase (std::string name);
+ virtual ~TestCase ();
+
+ /**
+ * \brief Run this test case.
+ * \param verbose Turn on any output the test case may provide
+ * \returns Boolean sense of "an error has occurred."
+ */
+ bool Run (void);
+
+ /**
+ * \brief Set the verbosity of this test case.
+ * \param verbose Whether or not to print "stuff."
+ */
+ void SetVerbose (bool verbose);
+
+ /**
+ * \brief Set the name of this test case.
+ */
+ void SetName (std::string name);
+
+ /**
+ * \brief Get the name of this test case.
+ */
+ std::string GetName (void);
+
+ /**
+ * \brief Set the base directory of the ns-3 distribution.
+ */
+ void SetBaseDir (std::string dir);
+
+ /**
+ * \brief Get the base directory of the ns-3 distribution.
+ */
+ std::string GetBaseDir (void);
+
+/**
+ * \brief Get the source directory of the current source file.
+ *
+ * One of the basic models of the test environment is that dedicated test-
+ * and response vectors live in the same directory as the source file. So it
+ * is a common operation to figure out what directory a given source file lives
+ * in.
+ *
+ * __FILE__ provides almost all of what we need, but in the gnu toolchain it
+ * comes out as something like "../src/core/pcap-file-test-suite.cc".
+ *
+ * We really don't want to have any dependency on the directory out of which a
+ * test is run, so we ask that any test-runner give us the base directory of the
+ * distribution, which is set via TestCase::SetBaseDir(). That string will look
+ * something like "/home/user/repos/ns-3-allinone/ns-3-dev".
+ *
+ * This function stitches the two pieces together and removes the file name to
+ * return something like "/home/user/repos/ns-3-allinone/ns-3-dev/src/core/".
+ *
+ * \param file The current source file name relative to the base directory.
+ * \returns The current source directory.
+ *
+ * \warning Always call this function as GetSourceDir (__FILE__) or use the
+ * convenience macro NS_TEST_SOURCEDIR.
+ */
+ std::string GetSourceDir (std::string file);
+
+ /**
+ * \brief Set the stream to which status and result messages will be written.
+ *
+ * We really don't want to have to pass an ofstream around to every function
+ * and we especially don't want to have to make our clients plumb an ofstream
+ * around so we need to save it. Since file streams are not designed to be
+ * copied or assigned (what does it mean to have duplicate streams to a file)
+ * we have to stash a pointer to the stream.
+ */
+ void SetStream (std::ofstream *ofs);
+
+ /**
+ * \brief Get the stream to which status and result messages will be written.
+ */
+ std::ofstream *GetStream (void);
+
+ /**
+ * \brief Manually Set the error status of this test case.
+ */
+ void SetErrorStatus (bool error);
+
+ /**
+ * \brief Get the error status of this test case.
+ */
+ bool GetErrorStatus (void);
+
+ void ReportStart (void);
+ void ReportSuccess (void);
+ void ReportFailure (std::string cond, std::string actual, std::string limit, std::string message,
+ std::string file, int32_t line);
+ void ReportEnd (void);
+
+protected:
+ /**
+ * \internal
+ * \brief Implementation of reporting method for the start of the test case.
+ */
+ virtual void DoReportStart (void);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for success of the test case.
+ */
+ virtual void DoReportSuccess (void);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for failure of the test case.
+ */
+ virtual void DoReportFailure (std::string cond, std::string actual, std::string limit, std::string message,
+ std::string file, int32_t line);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for the end of the test case.
+ */
+ virtual void DoReportEnd (void);
+
+ /**
+ * \internal
+ * \param verbose Turn on any output the test case may provide
+ * \brief Implementation to do any local setup required for this test case.
+ */
+ virtual void DoSetup (void);
+
+ /**
+ * \internal
+ * \brief Implementation to actually run this test case.
+ * \param verbose Turn on any output the test case may provide
+ * \returns Boolean sense of "an error has occurred."
+ */
+ virtual bool DoRun (void) = 0;
+
+ /**
+ * \internal
+ * \param verbose Turn on any output the test case may provide
+ * \brief Implementation to do any local setup required for this test case.
+ */
+ virtual void DoTeardown (void);
+
+private:
+ TestCase (TestCase& tc);
+ TestCase& operator= (TestCase& tc);
+
+ std::string m_name;
+ bool m_verbose;
+ std::string m_basedir;
+ std::ofstream *m_ofs;
+ bool m_error;
+ clock_t m_startTime;
+ struct tms m_startTimes;
+};
+
+/**
+ * \brief A suite of tests to run.
+ */
+class TestSuite
+{
+public:
+ typedef enum TestType {
+ BVT = 1, /**< This test suite implements a Build Verification Test */
+ UNIT, /**< This test suite implements a Unit Test */
+ SYSTEM, /**< This test suite implements a System Test */
+ EXAMPLE, /**< This test suite implements an Example Test */
+ PERFORMANCE /**< This test suite implements a Performance Test */
+ };
+
+ /**
+ * \brief Constuct a new test suite.
+ *
+ * \param name The name of the test suite.
+ * \param type The TestType of the test suite (defaults to UNIT test).
+ */
+ TestSuite (std::string name, TestType type = UNIT);
+
+ /**
+ * \brief Destroy a test suite.
+ */
+ virtual ~TestSuite ();
+
+ /**
+ * \brief Run this test suite.
+ *
+ * \param verbose Turn on any output the test case may provide
+ * \returns Boolean sense of "an error has occurred."
+ */
+ bool Run (void);
+
+ /**
+ * \brief Add an individual test case to this test suite.
+ *
+ * \param testCase Pointer to the test case object to be added.
+ * \returns Integer assigned as identifer of the provided test case.
+ */
+ uint32_t AddTestCase (TestCase *testCase);
+
+ /**
+ * \brief Get the number of test cases that have been added to this test suite.
+ *
+ * \returns Number of test cases in the suite.
+ */
+ uint32_t GetNTestCases (void);
+
+ /**
+ * \brief Get the test case at index i.
+ */
+ TestCase *GetTestCase (uint32_t i);
+
+ /**
+ * \brief get the kind of test this test suite implements
+ *
+ * \returns the TestType of the suite.
+ */
+ TestType GetTestType (void);
+
+ /**
+ * \brief Set the verbosity of this test suite.
+ * \param verbose Whether or not to print "stuff."
+ */
+ void SetVerbose (bool verbose);
+
+ /**
+ * \brief Set the name of this test suite.
+ */
+ void SetName (std::string name);
+
+ /**
+ * \brief Get the name of this test suite.
+ */
+ std::string GetName (void);
+
+ /**
+ * \brief Set the base directory of the ns-3 distribution.
+ */
+ void SetBaseDir (std::string basedir);
+
+ /**
+ * \brief Get the base directory of the ns-3 distribution.
+ */
+ std::string GetBaseDir (void);
+
+ /**
+ * \brief Set the stream to which status and result messages will be written.
+ *
+ * We really don't want to have to pass an ofstream around to every function
+ * and we especially don't want to have to make our clients plumb an ofstream
+ * around so we need to save it. Since file streams are not designed to be
+ * copied or assigned (what does it mean to have duplicate streams to a file)
+ * we have to stash a pointer to the stream.
+ */
+ void SetStream (std::ofstream *ofs);
+
+ void ReportStart (void);
+ void ReportSuccess (void);
+ void ReportFailure (void);
+ void ReportEnd (void);
+
+protected:
+ /**
+ * \internal
+ * \brief Implementation of reporting method for the start of the test suite.
+ */
+ virtual void DoReportStart (void);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for success of the test suite.
+ */
+ virtual void DoReportSuccess (void);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for failure of the test suite.
+ */
+ virtual void DoReportFailure (void);
+
+ /**
+ * \internal
+ * \brief Implementation of reporting method for the end of the test suite.
+ */
+ virtual void DoReportEnd (void);
+
+ /**
+ * \internal
+ * \param verbose Turn on any output the test case may provide
+ * \brief Implementation to do any local setup required for this test suite.
+ */
+ virtual void DoSetup (void);
+
+ /**
+ * \internal
+ * \brief Implementation to actually run this test suite.
+ * \param verbose Turn on any output the test case may provide
+ * \returns Boolean sense of "an error has occurred."
+ */
+ virtual bool DoRun (void);
+
+ /**
+ * \internal
+ * \param verbose Turn on any output the test case may provide
+ * \brief Implementation to do any local setup required for this test suite.
+ */
+ virtual void DoTeardown (void);
+
+private:
+ TestSuite (TestSuite& ts);
+ TestSuite& operator= (TestSuite& ts);
+
+ std::string m_name;
+ bool m_verbose;
+ std::string m_basedir;
+ std::ofstream *m_ofs;
+ TestType m_type;
+
+ clock_t m_startTime;
+ struct tms m_startTimes;
+
+ typedef std::vector<TestCase *> TestCaseVector_t;
+ TestCaseVector_t m_tests;
+};
+
+/**
+ * \brief A runner to execute tests.
+ */
+class TestRunner
+{
+public:
+ static uint32_t AddTestSuite (TestSuite *testSuite);
+ static uint32_t GetNTestSuites (void);
+ static TestSuite *GetTestSuite (uint32_t n);
+};
+
+/**
+ * \brief A simple way to store test vectors (for stimulus or from responses)
+ */
+template <typename T>
+class TestVectors
+{
+public:
+ TestVectors ();
+ virtual ~TestVectors ();
+
+ void Reserve (uint32_t reserve);
+
+ uint32_t Add (T vector);
+
+ uint32_t GetN (void) const;
+ T Get (uint32_t i) const;
+
+private:
+ TestVectors (const TestVectors& tv);
+ TestVectors& operator= (const TestVectors& tv);
+ bool operator== (const TestVectors& tv) const;
+
+ typedef std::vector<T> TestVector_t;
+ TestVector_t m_vectors;
+};
+
+template <typename T>
+TestVectors<T>::TestVectors ()
+ : m_vectors ()
+{
+}
+
+template <typename T>
+void
+TestVectors<T>::Reserve (uint32_t reserve)
+{
+ m_vectors.reserve (reserve);
+}
+
+template <typename T>
+TestVectors<T>::~TestVectors ()
+{
+}
+
+template <typename T>
+uint32_t
+TestVectors<T>::Add (T vector)
+{
+ uint32_t index = m_vectors.size ();
+ m_vectors.push_back (vector);
+ return index;
+}
+
+template <typename T>
+uint32_t
+TestVectors<T>::GetN (void) const
+{
+ return m_vectors.size ();
+}
+
+template <typename T>
+T
+TestVectors<T>::Get (uint32_t i) const
+{
+ NS_ABORT_MSG_UNLESS (m_vectors.size () > i, "TestVectors::Get(): Bad index");
+ return m_vectors[i];
+}
+
+}; // namespace ns3
+
+//
+// Original ns-3 unit test code for compatibility
+//
#ifdef RUN_SELF_TESTS
namespace ns3 {
--- a/src/core/wscript Thu Sep 10 11:41:33 2009 +0100
+++ b/src/core/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -58,6 +58,7 @@
'test.cc',
'random-variable.cc',
'rng-stream.cc',
+ 'rng-test-suite.cc',
'command-line.cc',
'type-name.cc',
'type-traits-test.cc',
@@ -79,6 +80,7 @@
'callback.cc',
'names.cc',
'vector.cc',
+ 'names-test-suite.cc',
]
headers = bld.new_task_gen('ns3header')
--- a/src/helper/internet-stack-helper.h Thu Sep 10 11:41:33 2009 +0100
+++ b/src/helper/internet-stack-helper.h Sat Sep 12 19:44:17 2009 -0700
@@ -26,6 +26,8 @@
#include "ns3/packet.h"
#include "ns3/ptr.h"
#include "ns3/object-factory.h"
+#include "ns3/pcap-writer.h"
+#include "ns3/ascii-writer.h"
namespace ns3 {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/ns3tcp-cwnd-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,381 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 University of Washington
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ns3/log.h"
+#include "ns3/abort.h"
+#include "ns3/test.h"
+#include "ns3/pcap-file.h"
+#include "ns3/config.h"
+#include "ns3/string.h"
+#include "ns3/uinteger.h"
+#include "ns3/data-rate.h"
+#include "ns3/inet-socket-address.h"
+#include "ns3/point-to-point-helper.h"
+#include "ns3/internet-stack-helper.h"
+#include "ns3/ipv4-address-helper.h"
+#include "ns3/packet-sink-helper.h"
+#include "ns3/tcp-socket-factory.h"
+#include "ns3/simulator.h"
+
+using namespace ns3;
+
+NS_LOG_COMPONENT_DEFINE ("Ns3CwndTest");
+
+// ===========================================================================
+// This is a simple test to demonstrate how a known good model (a reference
+// implementation) may be used to test another model without resorting to
+// storing stimulus or response vectors.
+//
+// Node zero contains the model under test, in this case the ns-3 TCP
+// implementation. Node one contains the reference implementation that we
+// assume will generate good test vectors for us. In this case, a Linux
+// TCP implementation is used to stimulate the ns-3 TCP model with what we
+// assume are perfectly good packets. We watch the ns-3 implementation to
+// see what it does in the presence of these assumed good stimuli.
+//
+// The test is arranged as a typical ns-3 script, but we use the trace system
+// to peek into the running system and monitor the ns-3 TCP.
+//
+// The topology is just two nodes communicating over a point-to-point network.
+// The point-to-point network is chosen because it is simple and allows us to
+// easily generate pcap traces we can use to separately verify that the ns-3
+// implementation is responding correctly. Once the oopration is verified, we
+// enter a list of responses that capture the response succinctly.
+//
+// node 0 node 1
+// +----------------+ +----------------+
+// | ns-3 TCP | | Linux TCP |
+// +----------------+ +----------------+
+// | 10.1.1.1 | | 10.1.1.2 |
+// +----------------+ +----------------+
+// | point-to-point | | point-to-point |
+// +----------------+ +----------------+
+// | |
+// +---------------------+
+// 5 Mbps, 2 ms
+//
+// ===========================================================================
+//
+class SimpleSource : public Application
+{
+public:
+
+ SimpleSource ();
+ virtual ~SimpleSource();
+
+ void Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate);
+
+private:
+ virtual void StartApplication (void);
+ virtual void StopApplication (void);
+
+ void ScheduleTx (void);
+ void SendPacket (void);
+
+ Ptr<Socket> m_socket;
+ Address m_peer;
+ uint32_t m_packetSize;
+ uint32_t m_nPackets;
+ DataRate m_dataRate;
+ EventId m_sendEvent;
+ bool m_running;
+ uint32_t m_packetsSent;
+};
+
+SimpleSource::SimpleSource ()
+ : m_socket (0),
+ m_peer (),
+ m_packetSize (0),
+ m_nPackets (0),
+ m_dataRate (0),
+ m_sendEvent (),
+ m_running (false),
+ m_packetsSent (0)
+{
+}
+
+SimpleSource::~SimpleSource()
+{
+ m_socket = 0;
+}
+
+void
+SimpleSource::Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate)
+{
+ m_socket = socket;
+ m_peer = address;
+ m_packetSize = packetSize;
+ m_nPackets = nPackets;
+ m_dataRate = dataRate;
+}
+
+void
+SimpleSource::StartApplication (void)
+{
+ m_running = true;
+ m_packetsSent = 0;
+ m_socket->Bind ();
+ m_socket->Connect (m_peer);
+ SendPacket ();
+}
+
+void
+SimpleSource::StopApplication (void)
+{
+ m_running = false;
+
+ if (m_sendEvent.IsRunning ())
+ {
+ Simulator::Cancel (m_sendEvent);
+ }
+
+ if (m_socket)
+ {
+ m_socket->Close ();
+ }
+}
+
+void
+SimpleSource::SendPacket (void)
+{
+ Ptr<Packet> packet = Create<Packet> (m_packetSize);
+ m_socket->Send (packet);
+
+ if (++m_packetsSent < m_nPackets)
+ {
+ ScheduleTx ();
+ }
+}
+
+void
+SimpleSource::ScheduleTx (void)
+{
+ if (m_running)
+ {
+ Time tNext (Seconds (m_packetSize * 8 / static_cast<double> (m_dataRate.GetBitRate ())));
+ m_sendEvent = Simulator::Schedule (tNext, &SimpleSource::SendPacket, this);
+ }
+}
+
+class Ns3TcpCwndTestCase : public TestCase
+{
+public:
+ Ns3TcpCwndTestCase ();
+ virtual ~Ns3TcpCwndTestCase ();
+
+private:
+ virtual bool DoRun (void);
+ bool m_writeResults;
+
+ class CwndEvent {
+ public:
+ uint32_t m_oldCwnd;
+ uint32_t m_newCwnd;
+ };
+
+ TestVectors<CwndEvent> m_responses;
+
+ void CwndChange (uint32_t oldCwnd, uint32_t newCwnd);
+};
+
+Ns3TcpCwndTestCase::Ns3TcpCwndTestCase ()
+ : TestCase ("Check to see that the ns-3 TCP congestion window works as expected against liblinux2.6.26.so"),
+ m_writeResults (false)
+{
+}
+
+Ns3TcpCwndTestCase::~Ns3TcpCwndTestCase ()
+{
+}
+
+void
+Ns3TcpCwndTestCase::CwndChange (uint32_t oldCwnd, uint32_t newCwnd)
+{
+ CwndEvent event;
+
+ event.m_oldCwnd = oldCwnd;
+ event.m_newCwnd = newCwnd;
+
+ m_responses.Add (event);
+}
+
+bool
+Ns3TcpCwndTestCase::DoRun (void)
+{
+ //
+ // Just create two nodes. One (node zero) will be the node with the TCP
+ // under test which is the ns-3 TCP implementation. The other node (node
+ // one) will be the node with the reference implementation we use to drive
+ // the tests.
+ //
+ NodeContainer nodes;
+ nodes.Create (2);
+
+ //
+ // For this test we'll use a point-to-point net device. It's not as simple
+ // as a simple-net-device, but it provides nice places to hook trace events
+ // so we can see what's moving between our nodes.
+ //
+ PointToPointHelper pointToPoint;
+ pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
+ pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
+
+ //
+ // Install the point-to-point devices on both nodes and connec them up.
+ //
+ NetDeviceContainer devices;
+ devices = pointToPoint.Install (nodes);
+
+ //
+ // Install two variants of the internet stack. The first, on node zero
+ // uses the TCP under test, which is the default ns-3 TCP implementation.
+ //
+ InternetStackHelper stack;
+ stack.Install (nodes.Get (0));
+
+ //
+ // The other node, node one, is going to be set up to use a Linux TCP
+ // implementation that we consider a known good TCP.
+ //
+ std::string nscStack = "liblinux2.6.26.so";
+ stack.SetTcp ("ns3::NscTcpL4Protocol", "Library", StringValue("liblinux2.6.26.so"));
+ stack.Install (nodes.Get (1));
+
+ //
+ // Assign the address 10.1.1.1 to the TCP implementation under test (index
+ // zero) and 10.1.1.2 to the reference implementation (index one).
+ //
+ Ipv4AddressHelper address;
+ address.SetBase ("10.1.1.0", "255.255.255.252");
+ Ipv4InterfaceContainer interfaces = address.Assign (devices);
+
+ //
+ // We need a place to send our TCP data on the node with the reference TCP
+ // implementation. We aren't really concerned about what happens there, so
+ // just create a sink.
+ //
+ uint16_t sinkPort = 8080;
+ Address sinkAddress (InetSocketAddress(interfaces.GetAddress (1), sinkPort));
+ PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
+ ApplicationContainer sinkApps = packetSinkHelper.Install (nodes.Get (1));
+ sinkApps.Start (Seconds (0.));
+ sinkApps.Stop (Seconds (1.1));
+
+ //
+ // We want to look at changes in the ns-3 TCP congestion window. The
+ // congestion window is flow clontrol imposed by the sender, so we need
+ // to crank up a flow from the ns-3 TCP node to the NSC TCP node and hook the
+ // CongestionWindow attribute on the socket. Normally one would use an on-off
+ // application to generate a flow, but this has a couple of problems. First,
+ // the socket of the on-off application is not created until Application Start
+ // time, so we wouldn't be able to hook the socket now at configuration time.
+ // Second, even if we could arrange a call after start time, the socket is not
+ // public.
+ //
+ // So, we can cook up a simple version of the on-off application that does what
+ // we want. On the plus side we don't need all of the complexity of the on-off
+ // application. On the minus side, we don't have a helper, so we have to get
+ // a little more involved in the details, but this is trivial.
+ //
+ // So first, we create a socket and do the trace connect on it; then we pass this
+ // socket into the constructor of our simple application which we then install
+ // in the node with the ns-3 TCP.
+ //
+ Ptr<Socket> ns3TcpSocket = Socket::CreateSocket (nodes.Get (0), TcpSocketFactory::GetTypeId ());
+ ns3TcpSocket->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&Ns3TcpCwndTestCase::CwndChange, this));
+
+ Ptr<SimpleSource> app = CreateObject<SimpleSource> ();
+ app->Setup (ns3TcpSocket, sinkAddress, 1040, 10, DataRate ("5Mbps"));
+ nodes.Get (0)->AddApplication (app);
+ app->Start (Seconds (1.));
+ app->Stop (Seconds (1.1));
+
+ //
+ // The idea here is that someone will look very closely at the all of the
+ // communications between the reference TCP and the TCP under test in this
+ // simulation and determine that all of the responses are correct. We expect
+ // that this means generating a pcap trace file from the point-to-point link
+ // and examining the packets closely using tcpdump, wireshark or some such
+ // program. So we provide the ability to generate a pcap trace of the
+ // test execution for your perusal.
+ //
+ // Once the validation test is determined to be running exactly as exptected,
+ // the set of congestion window changes is collected and hard coded into the
+ // test results which will then be checked during the actual execution of the
+ // test.
+ //
+
+ if (m_writeResults)
+ {
+ PointToPointHelper::EnablePcapAll ("tcp-cwnd");
+ }
+
+ Simulator::Stop (Seconds(2));
+ Simulator::Run ();
+ Simulator::Destroy ();
+
+ //
+ // As new acks are received by the TCP under test, the congestion window
+ // should be opened up by one segment (MSS bytes) each time. This should
+ // trigger a congestion window change event which we hooked and saved above.
+ // We should now be able to look through the saved response vectors and follow
+ // the congestion window as it opens up when the ns-3 TCP under test
+ // transmits its bits
+ //
+ // From inspecting the results, we know that we should see N_EVENTS congestion
+ // window change events. The window should expand N_EVENTS - 1 times (each
+ // time by MSS bytes) until it gets to its largest value. Then the application
+ // sending stops and the window should be slammed shut, with the last event
+ // reflecting the change from LARGEST_CWND back to MSS
+ //
+ const uint32_t MSS = 536;
+ const uint32_t N_EVENTS = 21;
+ const uint32_t LARGEST_CWND = MSS * N_EVENTS;
+
+ CwndEvent event;
+
+ NS_TEST_ASSERT_MSG_EQ (m_responses.GetN (), N_EVENTS, "Unexpectedly low number of cwnd change events");
+
+
+ for (uint32_t i = 0, from = 536, to = 1072; i < N_EVENTS - 1; ++i, from += 536, to += 536)
+ {
+ event = m_responses.Get (i);
+ NS_TEST_ASSERT_MSG_EQ (event.m_oldCwnd, from, "Wrong old cwnd value in cwnd change event " << i);
+ NS_TEST_ASSERT_MSG_EQ (event.m_newCwnd, to, "Wrong new cwnd value in cwnd change event " << i);
+ }
+
+ event = m_responses.Get (N_EVENTS - 1);
+ NS_TEST_ASSERT_MSG_EQ (event.m_oldCwnd, LARGEST_CWND, "Wrong old cwnd value in cwnd change event " << N_EVENTS - 1);
+ NS_TEST_ASSERT_MSG_EQ (event.m_newCwnd, MSS, "Wrong new cwnd value in cwnd change event " << N_EVENTS - 1);
+
+ return GetErrorStatus ();
+}
+
+class Ns3TcpCwndTestSuite : public TestSuite
+{
+public:
+ Ns3TcpCwndTestSuite ();
+};
+
+Ns3TcpCwndTestSuite::Ns3TcpCwndTestSuite ()
+ : TestSuite ("ns3-tcp-cwnd", SYSTEM)
+{
+ AddTestCase (new Ns3TcpCwndTestCase);
+}
+
+Ns3TcpCwndTestSuite ns3TcpCwndTestSuite;
Binary file src/test/ns3tcp/ns3tcp-interop-response-vectors.pcap has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/ns3tcp-interop-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,307 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 University of Washington
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ns3/log.h"
+#include "ns3/abort.h"
+#include "ns3/test.h"
+#include "ns3/pcap-file.h"
+#include "ns3/config.h"
+#include "ns3/string.h"
+#include "ns3/uinteger.h"
+#include "ns3/inet-socket-address.h"
+#include "ns3/point-to-point-helper.h"
+#include "ns3/internet-stack-helper.h"
+#include "ns3/ipv4-address-helper.h"
+#include "ns3/ipv4-header.h"
+#include "ns3/packet-sink-helper.h"
+#include "ns3/on-off-helper.h"
+#include "ns3/simulator.h"
+
+using namespace ns3;
+
+NS_LOG_COMPONENT_DEFINE ("Ns3TcpInteropTest");
+
+const bool WRITE_VECTORS = false; // hack hack hack
+const uint32_t PCAP_LINK_TYPE = 1187373553; // Some large random number -- we use to verify data was written by this program
+const uint32_t PCAP_SNAPLEN = 64; // Don't bother to save much data
+
+
+// ===========================================================================
+// This is a simple test to demonstrate how a known good model (a reference
+// implementation) may be used to test another model in a relatively simple
+// way.
+//
+// Node zero contains the model under test, in this case the ns-3 TCP
+// implementation. Node one contains the reference implementation that we
+// assume will generate good test vectors for us. In this case, a Linux
+// TCP implementation is used to stimulate the ns-3 TCP model with what we
+// assume are perfectly good packets. We watch the ns-3 implementation to
+// see what it does in the presence of these assumed good stimuli.
+//
+// The test is arranged as a typical ns-3 script, but we use the trace system
+// to peek into the running system and monitor the ns-3 TCP.
+//
+// The topology is just two nodes communicating over a point-to-point network.
+// The point-to-point network is chosen because it is simple and allows us to
+// easily generate pcap traces we can use to separately verify that the ns-3
+// implementation is responding correctly. Once the opration is verified, we
+// capture a set of response vectors that are then checked in the test to
+// ensure that the ns-3 TCP continues to respond correctly over time.
+//
+// node 0 node 1
+// +----------------+ +----------------+
+// | ns-3 TCP | | Linux TCP |
+// +----------------+ +----------------+
+// | 10.1.1.1 | | 10.1.1.2 |
+// +----------------+ +----------------+
+// | point-to-point | | point-to-point |
+// +----------------+ +----------------+
+// | |
+// +---------------------+
+// 5 Mbps, 2 ms
+//
+// ===========================================================================
+class Ns3TcpInteroperabilityTestCase : public TestCase
+{
+public:
+ Ns3TcpInteroperabilityTestCase ();
+ virtual ~Ns3TcpInteroperabilityTestCase ();
+
+private:
+ virtual void DoSetup (void);
+ virtual bool DoRun (void);
+ virtual void DoTeardown (void);
+
+ void Ipv4L3Tx (std::string context, Ptr<const Packet> packet, uint32_t interfaceIndex);
+
+ std::string m_pcapFilename;
+ PcapFile m_pcapFile;
+ bool m_writeVectors;
+};
+
+Ns3TcpInteroperabilityTestCase::Ns3TcpInteroperabilityTestCase ()
+ : TestCase ("Check to see that the ns-3 TCP can work with liblinux2.6.26.so"), m_writeVectors(WRITE_VECTORS)
+{
+}
+
+Ns3TcpInteroperabilityTestCase::~Ns3TcpInteroperabilityTestCase ()
+{
+}
+
+void
+Ns3TcpInteroperabilityTestCase::DoSetup (void)
+{
+ //
+ // We expect there to be a file called tcp-interop-response-vectors.pcap" in
+ // the source directory of this file.
+ //
+ m_pcapFilename = NS_TEST_SOURCEDIR + "ns3tcp-interop-response-vectors.pcap";
+
+ if (m_writeVectors)
+ {
+ m_pcapFile.Open (m_pcapFilename, "w");
+ m_pcapFile.Init(PCAP_LINK_TYPE, PCAP_SNAPLEN);
+ }
+ else
+ {
+ m_pcapFile.Open (m_pcapFilename, "r");
+ NS_ABORT_MSG_UNLESS (m_pcapFile.GetDataLinkType () == PCAP_LINK_TYPE, "Wrong response vectors in directory");
+ }
+}
+
+void
+Ns3TcpInteroperabilityTestCase::DoTeardown (void)
+{
+ m_pcapFile.Close ();
+}
+
+void
+Ns3TcpInteroperabilityTestCase::Ipv4L3Tx (std::string context, Ptr<const Packet> packet, uint32_t interfaceIndex)
+{
+ //
+ // We're not testing IP so remove and toss the header. In order to do this,
+ // though, we need to copy the packet since we have a const version.
+ //
+ Ptr<Packet> p = packet->Copy ();
+ Ipv4Header ipHeader;
+ p->RemoveHeader (ipHeader);
+
+ //
+ // What is left is the TCP header and any data that may be sent. We aren't
+ // sending any TCP data, so we expect what remains is only TCP header, which
+ // is a small thing to save.
+ //
+ if (m_writeVectors)
+ {
+ //
+ // Save the TCP under test response for later testing.
+ //
+ Time tNow = Simulator::Now ();
+ int64_t tMicroSeconds = tNow.GetMicroSeconds ();
+ m_pcapFile.Write (uint32_t (tMicroSeconds / 1000000),
+ uint32_t (tMicroSeconds % 1000000),
+ p->PeekData(),
+ p->GetSize ());
+ }
+ else
+ {
+ //
+ // Read the TCP under test expected response from the expected vector
+ // file and see if it still does the right thing.
+ //
+ uint8_t expected[PCAP_SNAPLEN];
+ uint32_t tsSec, tsUsec, inclLen, origLen, readLen;
+ m_pcapFile.Read (expected, sizeof(expected), tsSec, tsUsec, inclLen, origLen, readLen);
+
+ uint8_t const *actual = p->PeekData();
+
+ uint32_t result = memcmp(actual, expected, readLen);
+
+ //
+ // Avoid streams of errors -- only report the first.
+ //
+ if (GetErrorStatus () == false)
+ {
+ NS_TEST_EXPECT_MSG_EQ (result, 0, "Expected data comparison error");
+ }
+ }
+}
+
+bool
+Ns3TcpInteroperabilityTestCase::DoRun (void)
+{
+ //
+ // Just create two nodes. One (node zero) will be the node with the TCP
+ // under test which is the ns-3 TCP implementation. The other node (node
+ // one) will be the node with the reference implementation we use to drive
+ // the tests.
+ //
+ NodeContainer nodes;
+ nodes.Create (2);
+
+ //
+ // For this test we'll use a point-to-point net device. It's not as simple
+ // as a simple-net-device, but it provides nice places to hook trace events
+ // so we can see what's moving between our nodes.
+ //
+ PointToPointHelper pointToPoint;
+ pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
+ pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
+
+ //
+ // Install the point-to-point devices on both nodes and connec them up.
+ //
+ NetDeviceContainer devices;
+ devices = pointToPoint.Install (nodes);
+
+ //
+ // Install two variants of the internet stack. The first, on node zero
+ // uses the TCP under test, which is the default ns-3 TCP implementation.
+ //
+ InternetStackHelper stack;
+ stack.Install (nodes.Get (0));
+
+ //
+ // The other node, node one, is going to be set up to use a Linux TCP
+ // implementation that we consider a known good TCP.
+ //
+ std::string nscStack = "liblinux2.6.26.so";
+ stack.SetTcp ("ns3::NscTcpL4Protocol", "Library", StringValue("liblinux2.6.26.so"));
+ stack.Install (nodes.Get (1));
+
+ //
+ // Assign the address 10.1.1.1 to the TCP implementation under test (index
+ // zero) and 10.1.1.2 to the reference implementation (index one).
+ //
+ Ipv4AddressHelper address;
+ address.SetBase ("10.1.1.0", "255.255.255.252");
+ Ipv4InterfaceContainer interfaces = address.Assign (devices);
+
+ //
+ // We need a place for the TCP data to go on the node with the TCP under
+ // test, so just create a sink on node zero.
+ //
+ uint16_t sinkPort = 8080;
+ Address sinkAddress (InetSocketAddress(interfaces.GetAddress (0), sinkPort));
+ PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
+ ApplicationContainer sinkApps = packetSinkHelper.Install (nodes.Get (0));
+ sinkApps.Start (Seconds (0.));
+
+ //
+ // We need something to shove data down the pipe, so we create an on-off
+ // application on the soure node with the reference TCP implementation.
+ // The default behavior is to send for one second, then go quiet for one
+ // second, and repeat.
+ //
+ OnOffHelper onOffHelper ("ns3::TcpSocketFactory", sinkAddress);
+ onOffHelper.SetAttribute ("MaxBytes", UintegerValue (100000));
+ ApplicationContainer sourceApps = onOffHelper.Install(nodes.Get(1));
+ sourceApps.Start (Seconds (1.));
+ sourceApps.Stop (Seconds (10.));
+
+ //
+ // There are currently a limited number of trace hooks in the ns-3 TCP code.
+ // Rather than editing TCP to insert a bunch of trace hooks, we can just
+ // intercept the packets at the IPv4 layer. See internet-stack-helper.cc
+ // for complete description of the trace hooks. We're interested in the
+ // responses of the TCP under test, which implies we need to hook the node
+ // zero Ipv4 layer three transmit trace source. We'll then get all of the
+ // responses we need
+ //
+ Config::Connect ("/NodeList/0/$ns3::Ipv4L3Protocol/Tx",
+ MakeCallback (&Ns3TcpInteroperabilityTestCase::Ipv4L3Tx, this));
+
+ //
+ // The idea here is that someone will look very closely at the all of the
+ // communications between the reference TCP and the TCP under test in this
+ // simulation and determine that all of the responses are correct. We expect
+ // that this means generating a pcap trace file from the point-to-point link
+ // and examining the packets closely using tcpdump, wireshark or some such
+ // program. So we provide the ability to generate a pcap trace of the
+ // test execution for your perusal.
+ //
+ // Once the validation test is determined to be running exactly as exptected,
+ // we allow you to generate a file that contains the response vectors that
+ // will be checked during the actual execution of the test.
+ //
+
+ if (m_writeVectors)
+ {
+ PointToPointHelper::EnablePcapAll ("tcp-interop");
+ }
+
+ Simulator::Stop (Seconds(20));
+ Simulator::Run ();
+ Simulator::Destroy ();
+
+ return GetErrorStatus ();
+}
+
+class Ns3TcpInteroperabilityTestSuite : public TestSuite
+{
+public:
+ Ns3TcpInteroperabilityTestSuite ();
+};
+
+Ns3TcpInteroperabilityTestSuite::Ns3TcpInteroperabilityTestSuite ()
+ : TestSuite ("ns3-tcp-interoperability", SYSTEM)
+{
+ AddTestCase (new Ns3TcpInteroperabilityTestCase);
+}
+
+Ns3TcpInteroperabilityTestSuite ns3TcpInteroperabilityTestSuite;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/ns3tcp.h Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,8 @@
+/**
+ * \ingroup tests
+ * \defgroup Ns3TcpTests ns-3 TCP Implementation Tests
+ *
+ * \section Ns3TcpTestsOverview ns-3 Tcp Implementation Tests Overview
+ *
+ * ns-3 has a TCP implemtation and we test it a litte.
+ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/waf Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,1 @@
+exec "`dirname "$0"`"/../../../waf "$@"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,16 @@
+## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+def configure(conf):
+ pass
+
+def build(bld):
+ ns3tcp = bld.create_ns3_module('ns3tcp')
+ ns3tcp.source = [
+ 'ns3tcp-interop-test-suite.cc',
+ 'ns3tcp-cwnd-test-suite.cc',
+ ]
+ headers = bld.new_task_gen('ns3header')
+ headers.module = 'ns3tcp'
+ headers.source = [
+ 'ns3tcp.h',
+ ]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3wifi/ns3wifi.h Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,8 @@
+/**
+ * \ingroup tests
+ * \defgroup Ns3WifiTests ns-3 Wifi Implementation Tests
+ *
+ * \section Ns3WifiTestsOverview ns-3 Wifi Implementation Tests Overview
+ *
+ * ns-3 has a Wifi implemtation and we test it a litte.
+ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3wifi/propagation-loss-models-test-suite.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,579 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 The Boeing Company
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ns3/log.h"
+#include "ns3/abort.h"
+#include "ns3/test.h"
+#include "ns3/pcap-file.h"
+#include "ns3/config.h"
+#include "ns3/string.h"
+#include "ns3/uinteger.h"
+#include "ns3/data-rate.h"
+#include "ns3/inet-socket-address.h"
+#include "ns3/internet-stack-helper.h"
+#include "ns3/ipv4-address-helper.h"
+#include "ns3/tcp-socket-factory.h"
+#include "ns3/yans-wifi-helper.h"
+#include "ns3/propagation-loss-model.h"
+#include "ns3/propagation-delay-model.h"
+#include "ns3/yans-wifi-channel.h"
+#include "ns3/yans-wifi-phy.h"
+#include "ns3/wifi-net-device.h"
+#include "ns3/mobility-helper.h"
+#include "ns3/constant-position-mobility-model.h"
+#include "ns3/nqos-wifi-mac-helper.h"
+#include "ns3/simulator.h"
+
+using namespace ns3;
+
+NS_LOG_COMPONENT_DEFINE ("Ns3WifiPropagationLossModelsTest");
+
+// ===========================================================================
+// This is a simple test to validate propagation loss models of ns-3 wifi.
+//
+// The basic configuration is,
+//
+// node 0 node 1
+// +------------+ +------------+
+// | ns-3 UDP | | ns-3 UDP |
+// +------------+ +------------+
+// | 10.1.1.1 | | 10.1.1.2 |
+// +------------+ +------------+
+// | wifi | | wifi |
+// +------------+ +------------+
+// | |
+// (((*))) (((*)))
+//
+// |<-- distance -->|
+//
+//
+// We vary the propagation loss model and the distance between the nodes,
+// looking at the received power and SNR for a packet sent between them. We
+// compare the found values with values found in an "authoritative source."
+// ===========================================================================
+//
+class Ns3FriisPropagationLossModelTestCase : public TestCase
+{
+public:
+ Ns3FriisPropagationLossModelTestCase ();
+ virtual ~Ns3FriisPropagationLossModelTestCase ();
+
+private:
+ virtual bool DoRun (void);
+
+ void SendPacket (uint32_t i, Ptr<Socket> socket, uint32_t size);
+ void Receive (Ptr<Packet> p, double snr, WifiMode mode, enum WifiPreamble preamble);
+
+ uint32_t m_gotCallbacks;
+
+ Ptr<Node> m_receiver;
+ uint32_t m_vectorIndex;
+
+ typedef struct {
+ Vector m_position;
+ double m_snr;
+ double m_tolerance;
+ } TestVector;
+
+ TestVectors<TestVector> m_testVectors;
+};
+
+Ns3FriisPropagationLossModelTestCase::Ns3FriisPropagationLossModelTestCase ()
+ : TestCase ("Check to see that the ns-3 Friis propagation loss model provides correct SNR values"),
+ m_gotCallbacks (false), m_receiver (0), m_vectorIndex (0), m_testVectors ()
+{
+}
+
+Ns3FriisPropagationLossModelTestCase::~Ns3FriisPropagationLossModelTestCase ()
+{
+}
+
+void
+Ns3FriisPropagationLossModelTestCase::Receive (Ptr<Packet> p, double snr, WifiMode mode, enum WifiPreamble preamble)
+{
+ TestVector testVector = m_testVectors.Get (m_vectorIndex++);
+ if (GetErrorStatus () == false)
+ {
+ NS_TEST_EXPECT_MSG_EQ_TOL (snr, testVector.m_snr, testVector.m_tolerance, "Got unexpected SNR value");
+ }
+ ++m_gotCallbacks;
+}
+
+void
+Ns3FriisPropagationLossModelTestCase::SendPacket (uint32_t i, Ptr<Socket> socket, uint32_t size)
+{
+ TestVector testVector = m_testVectors.Get (i);
+ m_receiver->GetObject<ConstantPositionMobilityModel> ()->SetPosition (testVector.m_position);
+ socket->Send (Create<Packet> (size));
+}
+
+bool
+Ns3FriisPropagationLossModelTestCase::DoRun (void)
+{
+ //
+ // We want to test the propagation loss model calculations at a few chosen
+ // distances and compare the results to those we have manually calculated
+ // according to the model documentation. The following "TestVector" objects
+ // will drive the test.
+ //
+ // For example, the first test vector provides a position to which the
+ // receiver node will be moved prior to transmitting a packet. It also
+ // specifies that when the packet is received, the SNR shold be found
+ // to be 1129.93 +- 0.005 in the ReceiveOkCallback.
+ //
+ TestVector testVector;
+
+ testVector.m_position = Vector (100, 0, 0);
+ testVector.m_snr = 1128.93;
+ testVector.m_tolerance = 0.005;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (500, 0, 0);
+ testVector.m_snr = 45.1571;
+ testVector.m_tolerance = 0.00005;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (1000, 0, 0);
+ testVector.m_snr = 11.2893;
+ testVector.m_tolerance = 0.00005;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (2000, 0, 0);
+ testVector.m_snr = 2.82232;
+ testVector.m_tolerance = 0.000005;
+ m_testVectors.Add (testVector);
+
+ //
+ // Disable fragmentation for frames shorter than 2200 bytes.
+ //
+ Config::SetDefault ("ns3::WifiRemoteStationManager::FragmentationThreshold", StringValue ("2200"));
+
+ //
+ // Turn off RTS/CTS for frames shorter than 2200 bytes.
+ //
+ Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue ("2200"));
+
+ //
+ // Create the two nodes in the system. Data will be sent from node zero to
+ // node one.
+ //
+ NodeContainer nodes;
+ nodes.Create (2);
+
+ //
+ // Save a Ptr<Node> to the receiver node so we can get at its mobility model
+ // and change its position (distance) later.
+ //
+ m_receiver = nodes.Get (1);
+
+ //
+ // Use the regular WifiHelper to orchestrate hooking the various pieces of
+ // the wifi system together. Tell it that we want to use an 802.11b phy.
+ //
+ WifiHelper wifi;
+ wifi.SetStandard (WIFI_PHY_STANDARD_80211b);
+
+ //
+ // Create a physical layer helper and tell it we don't want any receiver
+ // gain.
+ //
+ YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
+ wifiPhy.Set ("RxGain", DoubleValue (0) );
+
+ //
+ // Create the channel helper and tell it that signals will be moving at the
+ // speed of light.
+ //
+ YansWifiChannelHelper wifiChannel ;
+ wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
+
+ //
+ // The propagation loss model is one of our independent variables in the
+ // test.
+ //
+ wifiChannel.AddPropagationLoss ("ns3::FriisPropagationLossModel",
+ "Lambda", DoubleValue (0.125),
+ "SystemLoss", DoubleValue (1.));
+
+ //
+ // Create a yans wifi channel and tell the phy helper to use it.
+ //
+ wifiPhy.SetChannel (wifiChannel.Create ());
+
+ //
+ // Create a non-quality-of-service mac layer and set it to ad-hoc mode.
+ //
+ NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();
+ wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager",
+ "DataMode", StringValue ("wifib-1mbs"),
+ "ControlMode",StringValue ("wifib-1mbs"));
+ wifiMac.SetType ("ns3::AdhocWifiMac");
+
+ //
+ // Create the wifi devices.
+ //
+ NetDeviceContainer devices = wifi.Install (wifiPhy, wifiMac, nodes);
+
+ //
+ // We need to reach down into the receiving wifi device's phy layer and hook
+ // the appropriate trace event to get the snr. This isn't one of the usual
+ // events so it takes some poking around to get there from here.
+ //
+ Ptr<YansWifiPhy> phy = devices.Get (1)->GetObject<WifiNetDevice> ()->GetPhy ()->GetObject<YansWifiPhy> ();
+ phy->SetReceiveOkCallback (MakeCallback (&Ns3FriisPropagationLossModelTestCase::Receive, this));
+
+ //
+ // Add mobility models to both nodes. This is used to place the two nodes a
+ // fixed distance apart. Node zero (the sender) is always at the origin and
+ // Node one (the receiver) is moved along the x-axis to a given distance from
+ // the origin. This distance is the second independent variable in our test.
+ //
+ MobilityHelper mobility;
+ Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator> ();
+ positionAlloc->Add (Vector (0., 0., 0.));
+ positionAlloc->Add (Vector (0, 0., 0.));
+ mobility.SetPositionAllocator (positionAlloc);
+ mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
+ mobility.Install (nodes);
+
+ //
+ // In order to use UDP sockets, we need to install the ns-3 internet stack
+ // on our nodes.
+ //
+ InternetStackHelper internet;
+ internet.Install (nodes);
+
+ //
+ // Assign IP addresses to our nodes. The source node is going to end up
+ // as 10.1.1.1 and the destination will be 10.1.1.2
+ //
+ Ipv4AddressHelper addresses;
+ addresses.SetBase ("10.1.1.0", "255.255.255.0");
+ Ipv4InterfaceContainer interfaces = addresses.Assign (devices);
+
+ //
+ // The destination is the wifi device on node one.
+ //
+ InetSocketAddress destaddr = InetSocketAddress (interfaces.GetAddress (1), 80);
+
+ //
+ // We just want to send packets from the source to the destination node, so
+ // the simplest thing is to cook something up manually.
+ //
+ TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
+ Ptr<Socket> dest = Socket::CreateSocket (nodes.Get (1), tid);
+ dest->Bind (destaddr);
+
+ Ptr<Socket> source = Socket::CreateSocket (nodes.Get (0), tid);
+ source->Connect (destaddr);
+
+ //
+ // Schedule the packet sends, one packet per simulated second.
+ //
+ for (uint32_t i = 0; i < m_testVectors.GetN (); ++i)
+ {
+ Time t = Seconds (1. * i);
+ Simulator::Schedule (t, &Ns3FriisPropagationLossModelTestCase::SendPacket, this, i, source, 1000);
+ }
+
+ Simulator::Stop (Seconds(1. * m_testVectors.GetN ()));
+ Simulator::Run ();
+
+ source->Close ();
+ source = 0;
+
+ dest->Close ();
+ dest = 0;
+
+ m_receiver = 0;
+
+ Simulator::Destroy ();
+
+ //
+ // If we've already reported an error, just leave it at that.
+ //
+ if (GetErrorStatus () == false)
+ {
+ NS_TEST_ASSERT_MSG_EQ (m_gotCallbacks, m_testVectors.GetN (), "Did not get expected number of ReceiveOkCallbacks");
+ }
+
+ return GetErrorStatus ();
+}
+
+class Ns3LogDistancePropagationLossModelTestCase : public TestCase
+{
+public:
+ Ns3LogDistancePropagationLossModelTestCase ();
+ virtual ~Ns3LogDistancePropagationLossModelTestCase ();
+
+private:
+ virtual bool DoRun (void);
+
+ void SendPacket (uint32_t i, Ptr<Socket> socket, uint32_t size);
+ void Receive (Ptr<Packet> p, double snr, WifiMode mode, enum WifiPreamble preamble);
+
+ uint32_t m_gotCallbacks;
+
+ Ptr<Node> m_receiver;
+ uint32_t m_vectorIndex;
+
+ typedef struct {
+ Vector m_position;
+ double m_snr;
+ double m_tolerance;
+ } TestVector;
+
+ TestVectors<TestVector> m_testVectors;
+};
+
+Ns3LogDistancePropagationLossModelTestCase::Ns3LogDistancePropagationLossModelTestCase ()
+ : TestCase ("Check to see that the ns-3 Log Distance propagation loss model provides correct SNR values"),
+ m_gotCallbacks (false), m_receiver (0), m_vectorIndex (0), m_testVectors ()
+{
+}
+
+Ns3LogDistancePropagationLossModelTestCase::~Ns3LogDistancePropagationLossModelTestCase ()
+{
+}
+
+void
+Ns3LogDistancePropagationLossModelTestCase::Receive (Ptr<Packet> p, double snr, WifiMode mode, enum WifiPreamble preamble)
+{
+ TestVector testVector = m_testVectors.Get (m_vectorIndex++);
+ if (GetErrorStatus () == false)
+ {
+ NS_TEST_EXPECT_MSG_EQ_TOL (snr, testVector.m_snr, testVector.m_tolerance, "Got unexpected SNR value");
+ }
+ ++m_gotCallbacks;
+}
+
+void
+Ns3LogDistancePropagationLossModelTestCase::SendPacket (uint32_t i, Ptr<Socket> socket, uint32_t size)
+{
+ TestVector testVector = m_testVectors.Get (i);
+ m_receiver->GetObject<ConstantPositionMobilityModel> ()->SetPosition (testVector.m_position);
+ socket->Send (Create<Packet> (size));
+}
+
+bool
+Ns3LogDistancePropagationLossModelTestCase::DoRun (void)
+{
+ //
+ // We want to test the propagation loss model calculations at a few chosen
+ // distances and compare the results to those we have manually calculated
+ // according to the model documentation. The following "TestVector" objects
+ // will drive the test.
+ //
+ // For example, the first test vector provides a position to which the
+ // receiver node will be moved prior to transmitting a packet. It also
+ // specifies that when the packet is received, the SNR shold be found
+ // to be 1129.93 +- 0.005 in the ReceiveOkCallback.
+ //
+ TestVector testVector;
+
+ testVector.m_position = Vector (10, 0, 0);
+ testVector.m_snr = 11289.3;
+ testVector.m_tolerance = 0.05;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (20, 0, 0);
+ testVector.m_snr = 1411.16;
+ testVector.m_tolerance = 0.005;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (40, 0, 0);
+ testVector.m_snr = 176.407;
+ testVector.m_tolerance = 0.0005;
+ m_testVectors.Add (testVector);
+
+ testVector.m_position = Vector (80, 0, 0);
+ testVector.m_snr = 22.0494;
+ testVector.m_tolerance = 0.00005;
+ m_testVectors.Add (testVector);
+
+ //
+ // Disable fragmentation for frames shorter than 2200 bytes.
+ //
+ Config::SetDefault ("ns3::WifiRemoteStationManager::FragmentationThreshold", StringValue ("2200"));
+
+ //
+ // Turn off RTS/CTS for frames shorter than 2200 bytes.
+ //
+ Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue ("2200"));
+
+ //
+ // Create the two nodes in the system. Data will be sent from node zero to
+ // node one.
+ //
+ NodeContainer nodes;
+ nodes.Create (2);
+
+ //
+ // Save a Ptr<Node> to the receiver node so we can get at its mobility model
+ // and change its position (distance) later.
+ //
+ m_receiver = nodes.Get (1);
+
+ //
+ // Use the regular WifiHelper to orchestrate hooking the various pieces of
+ // the wifi system together. Tell it that we want to use an 802.11b phy.
+ //
+ WifiHelper wifi;
+ wifi.SetStandard (WIFI_PHY_STANDARD_80211b);
+
+ //
+ // Create a physical layer helper and tell it we don't want any receiver
+ // gain.
+ //
+ YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
+ wifiPhy.Set ("RxGain", DoubleValue (0) );
+
+ //
+ // Create the channel helper and tell it that signals will be moving at the
+ // speed of light.
+ //
+ YansWifiChannelHelper wifiChannel ;
+ wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
+
+ //
+ // The propagation loss model is one of our independent variables in the
+ // test.
+ //
+ wifiChannel.AddPropagationLoss ("ns3::LogDistancePropagationLossModel",
+ "Exponent", DoubleValue(3),
+ "ReferenceLoss", DoubleValue(40.045997));
+
+ //
+ // Create a yans wifi channel and tell the phy helper to use it.
+ //
+ wifiPhy.SetChannel (wifiChannel.Create ());
+
+ //
+ // Create a non-quality-of-service mac layer and set it to ad-hoc mode.
+ //
+ NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();
+ wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager",
+ "DataMode", StringValue ("wifib-1mbs"),
+ "ControlMode",StringValue ("wifib-1mbs"));
+ wifiMac.SetType ("ns3::AdhocWifiMac");
+
+ //
+ // Create the wifi devices.
+ //
+ NetDeviceContainer devices = wifi.Install (wifiPhy, wifiMac, nodes);
+
+ //
+ // We need to reach down into the receiving wifi device's phy layer and hook
+ // the appropriate trace event to get the snr. This isn't one of the usual
+ // events so it takes some poking around to get there from here.
+ //
+ Ptr<YansWifiPhy> phy = devices.Get (1)->GetObject<WifiNetDevice> ()->GetPhy ()->GetObject<YansWifiPhy> ();
+ phy->SetReceiveOkCallback (MakeCallback (&Ns3LogDistancePropagationLossModelTestCase::Receive, this));
+
+ //
+ // Add mobility models to both nodes. This is used to place the two nodes a
+ // fixed distance apart. Node zero (the sender) is always at the origin and
+ // Node one (the receiver) is moved along the x-axis to a given distance from
+ // the origin. This distance is the second independent variable in our test.
+ //
+ MobilityHelper mobility;
+ Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator> ();
+ positionAlloc->Add (Vector (0., 0., 0.));
+ positionAlloc->Add (Vector (0, 0., 0.));
+ mobility.SetPositionAllocator (positionAlloc);
+ mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
+ mobility.Install (nodes);
+
+ //
+ // In order to use UDP sockets, we need to install the ns-3 internet stack
+ // on our nodes.
+ //
+ InternetStackHelper internet;
+ internet.Install (nodes);
+
+ //
+ // Assign IP addresses to our nodes. The source node is going to end up
+ // as 10.1.1.1 and the destination will be 10.1.1.2
+ //
+ Ipv4AddressHelper addresses;
+ addresses.SetBase ("10.1.1.0", "255.255.255.0");
+ Ipv4InterfaceContainer interfaces = addresses.Assign (devices);
+
+ //
+ // The destination is the wifi device on node one.
+ //
+ InetSocketAddress destaddr = InetSocketAddress (interfaces.GetAddress (1), 80);
+
+ //
+ // We just want to send packets from the source to the destination node, so
+ // the simplest thing is to cook something up manually.
+ //
+ TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
+ Ptr<Socket> dest = Socket::CreateSocket (nodes.Get (1), tid);
+ dest->Bind (destaddr);
+
+ Ptr<Socket> source = Socket::CreateSocket (nodes.Get (0), tid);
+ source->Connect (destaddr);
+
+ //
+ // Schedule the packet sends, one packet per simulated second.
+ //
+ for (uint32_t i = 0; i < m_testVectors.GetN (); ++i)
+ {
+ Time t = Seconds (1. * i);
+ Simulator::Schedule (t, &Ns3LogDistancePropagationLossModelTestCase::SendPacket, this, i, source, 1000);
+ }
+
+ Simulator::Stop (Seconds(1. * m_testVectors.GetN ()));
+ Simulator::Run ();
+
+ source->Close ();
+ source = 0;
+
+ dest->Close ();
+ dest = 0;
+
+ m_receiver = 0;
+
+ Simulator::Destroy ();
+
+ //
+ // If we've already reported an error, just leave it at that.
+ //
+ if (GetErrorStatus () == false)
+ {
+ NS_TEST_ASSERT_MSG_EQ (m_gotCallbacks, m_testVectors.GetN (), "Did not get expected number of ReceiveOkCallbacks");
+ }
+
+ return GetErrorStatus ();
+}
+
+class Ns3WifiPropagationLossModelsTestSuite : public TestSuite
+{
+public:
+ Ns3WifiPropagationLossModelsTestSuite ();
+};
+
+Ns3WifiPropagationLossModelsTestSuite::Ns3WifiPropagationLossModelsTestSuite ()
+ : TestSuite ("ns3-wifi-propagation-loss-models", SYSTEM)
+{
+ AddTestCase (new Ns3FriisPropagationLossModelTestCase);
+ AddTestCase (new Ns3LogDistancePropagationLossModelTestCase);
+}
+
+Ns3WifiPropagationLossModelsTestSuite ns3WifiPropagationLossModelsTestSuite;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3wifi/waf Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,1 @@
+exec "`dirname "$0"`"/../../../waf "$@"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3wifi/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,15 @@
+## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+def configure(conf):
+ pass
+
+def build(bld):
+ ns3wifi = bld.create_ns3_module('ns3wifi')
+ ns3wifi.source = [
+ 'propagation-loss-models-test-suite.cc',
+ ]
+ headers = bld.new_task_gen('ns3header')
+ headers.module = 'ns3wifi'
+ headers.source = [
+ 'ns3wifi.h',
+ ]
--- a/src/wscript Thu Sep 10 11:41:33 2009 +0100
+++ b/src/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -38,6 +38,8 @@
'applications/v4ping',
'applications/ping6',
'applications/radvd',
+ 'test/ns3tcp',
+ 'test/ns3wifi',
)
def set_options(opt):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test.py Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,886 @@
+#! /usr/bin/env python
+## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+#
+# Copyright (c) 2009 University of Washington
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation;
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import os
+import sys
+import optparse
+import subprocess
+import multiprocessing
+import threading
+import Queue
+import signal
+import random
+import xml.dom.minidom
+
+#
+# XXX This should really be part of a waf command to list the configuration
+# items relative to optional ns-3 pieces.
+#
+# A list of interesting configuration items in the waf configuration
+# cache which we may be interested in when deciding on which examples
+# to run and how to run them. These are set by waf during the
+# configuration phase and the corresponding assignments are usually
+# found in the associated subdirectory wscript files.
+#
+interesting_config_items = [
+ "NS3_BUILDDIR",
+ "NS3_MODULE_PATH",
+ "ENABLE_EMU",
+ "ENABLE_GSL",
+ "ENABLE_GTK_CONFIG_STORE",
+ "ENABLE_LIBXML2",
+ "ENABLE_NSC",
+ "ENABLE_PYTHON_BINDINGS",
+ "ENABLE_PYTHON_SCANNING",
+ "ENABLE_REAL_TIME",
+ "ENABLE_STATIC_NS3",
+ "ENABLE_SUDO",
+ "ENABLE_TAP",
+ "ENABLE_THREADING",
+]
+
+#
+# A list of examples to run as smoke tests just to ensure that they remain
+# buildable and runnable over time. Also a condition under which to run
+# the example (from the waf configuration).
+#
+# XXX Should this not be read from a configuration file somewhere and not
+# hardcoded.
+#
+example_tests = [
+ ("run-tests", "False"),
+ ("csma-bridge", "True"),
+ ("csma-bridge-one-hop", "True"),
+ ("csma-broadcast", "True"),
+ ("csma-multicast", "True"),
+ ("csma-one-subnet", "True"),
+ ("csma-packet-socket", "True"),
+ ("csma-ping", "True"),
+ ("csma-raw-ip-socket", "True"),
+ ("csma-star", "True"),
+ ("dynamic-global-routing", "True"),
+ ("first", "True"),
+ ("global-routing-slash32", "True"),
+ ("hello-simulator", "True"),
+ ("mixed-global-routing", "True"),
+ ("mixed-wireless", "True"),
+ ("object-names", "True"),
+ ("realtime-udp-echo", "ENABLE_REAL_TIME == True"),
+ ("second", "True"),
+ ("simple-alternate-routing", "True"),
+ ("simple-error-model", "True"),
+ ("simple-global-routing", "True"),
+ ("simple-point-to-point-olsr", "True"),
+ ("simple-wifi-frame-aggregation", "True"),
+ ("star", "True"),
+ ("static-routing-slash32", "True"),
+ ("tcp-large-transfer", "True"),
+ ("tcp-nsc-zoo", "ENABLE_NSC == True"),
+ ("tcp-star-server", "True"),
+ ("test-ipv6", "True"),
+ ("third", "True"),
+ ("udp-echo", "True"),
+ ("wifi-wired-bridging", "True"),
+]
+
+#
+# Most of the examples produce gangs of trace files, so we want to find
+# somewhere to put them that won't pollute the current directory. One
+# obvious place is somewhere in /tmp.
+#
+TMP_TRACES_DIR = "/tmp/unchecked-traces"
+
+#
+# The test suites are going to want to output status. They are running
+# concurrently. This means that unless we are careful, the output of
+# the test suites will be interleaved. Rather than introducing a lock
+# file that could unintentionally start serializing execution, we ask
+# the tests to write their output to a temporary directory and then
+# put together the final output file when we "join" the test tasks back
+# to the main thread.
+#
+TMP_OUTPUT_DIR = "/tmp/testpy"
+
+def get_node_text(node):
+ for child in node.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ return child.nodeValue
+ return "None"
+
+#
+# A simple example of writing a text file with a test result summary.
+#
+def translate_to_text(results_file, text_file):
+ f = open(text_file, 'w')
+ dom = xml.dom.minidom.parse(results_file)
+ for suite in dom.getElementsByTagName("TestSuite"):
+ result = get_node_text(suite.getElementsByTagName("SuiteResult")[0])
+ name = get_node_text(suite.getElementsByTagName("SuiteName")[0])
+ time = get_node_text(suite.getElementsByTagName("SuiteTime")[0])
+ output = "%s: Test Suite \"%s\" (%s)\n" % (result, name, time)
+ f.write(output)
+ if result != "CRASH":
+ for case in suite.getElementsByTagName("TestCase"):
+ result = get_node_text(case.getElementsByTagName("CaseResult")[0])
+ name = get_node_text(case.getElementsByTagName("CaseName")[0])
+ time = get_node_text(case.getElementsByTagName("CaseTime")[0])
+ output = " %s: Test Case \"%s\" (%s)\n" % (result, name, time)
+ f.write(output)
+
+ if result == "FAIL":
+ f.write(" Details:\n")
+ f.write(" Message: %s\n" % get_node_text(case.getElementsByTagName("CaseMessage")[0]))
+ f.write(" Condition: %s\n" % get_node_text(case.getElementsByTagName("CaseCondition")[0]))
+ f.write(" Actual: %s\n" % get_node_text(case.getElementsByTagName("CaseActual")[0]))
+ f.write(" Limit: %s\n" % get_node_text(case.getElementsByTagName("CaseLimit")[0]))
+ f.write(" File: %s\n" % get_node_text(case.getElementsByTagName("CaseFile")[0]))
+ f.write(" Line: %s\n" % get_node_text(case.getElementsByTagName("CaseLine")[0]))
+
+ for example in dom.getElementsByTagName("Example"):
+ result = get_node_text(example.getElementsByTagName("Result")[0])
+ name = get_node_text(example.getElementsByTagName("Name")[0])
+ output = "%s: Example \"%s\"\n" % (result, name)
+ f.write(output)
+
+ f.close()
+
+#
+# A simple example of writing an HTML file with a test result summary.
+#
+def translate_to_html(results_file, html_file):
+ f = open(html_file, 'w')
+ f.write("<html>\n")
+ f.write("<body>\n")
+ f.write("<center><h1>ns-3 Test Results</h1></center>\n")
+
+ dom = xml.dom.minidom.parse(results_file)
+
+ f.write("<h2>Test Suites</h2>\n")
+ for suite in dom.getElementsByTagName("TestSuite"):
+ name = get_node_text(suite.getElementsByTagName("SuiteName")[0])
+ result = get_node_text(suite.getElementsByTagName("SuiteResult")[0])
+ time = get_node_text(suite.getElementsByTagName("SuiteTime")[0])
+
+ if result == "PASS":
+ f.write("<h3 style=\"color:green\">%s: %s (%s)</h3>\n" % (result, name, time))
+ else:
+ f.write("<h3 style=\"color:red\">%s: %s (%s)</h3>\n" % (result, name, time))
+
+
+ f.write("<table border=\"1\">\n")
+ f.write("<th> Result </th>\n")
+
+ if result == "CRASH":
+ f.write("<tr>\n")
+ f.write("<td style=\"color:red\">%s</td>\n" % result)
+ f.write("</tr>\n")
+ f.write("</table>\n")
+ continue
+
+ f.write("<th>Test Case Name</th>\n")
+ f.write("<th> Time </th>\n")
+
+ if result == "FAIL":
+ f.write("<th>Details</th>\n")
+
+ for case in suite.getElementsByTagName("TestCase"):
+ f.write("<tr>\n")
+ name = get_node_text(case.getElementsByTagName("CaseName")[0])
+ result = get_node_text(case.getElementsByTagName("CaseResult")[0])
+ time = get_node_text(case.getElementsByTagName("CaseTime")[0])
+ if result == "FAIL":
+ f.write("<td style=\"color:red\">%s</td>\n" % result)
+ f.write("<td>%s</td>\n" % name)
+ f.write("<td>%s</td>\n" % time)
+ f.write("<td>")
+ f.write("<b>Message: </b>%s, " % get_node_text(case.getElementsByTagName("CaseMessage")[0]))
+ f.write("<b>Condition: </b>%s, " % get_node_text(case.getElementsByTagName("CaseCondition")[0]))
+ f.write("<b>Actual: </b>%s, " % get_node_text(case.getElementsByTagName("CaseActual")[0]))
+ f.write("<b>Limit: </b>%s, " % get_node_text(case.getElementsByTagName("CaseLimit")[0]))
+ f.write("<b>File: </b>%s, " % get_node_text(case.getElementsByTagName("CaseFile")[0]))
+ f.write("<b>Line: </b>%s" % get_node_text(case.getElementsByTagName("CaseLine")[0]))
+ f.write("</td>\n")
+ else:
+ f.write("<td style=\"color:green\">%s</td>\n" % result)
+ f.write("<td>%s</td>\n" % name)
+ f.write("<td>%s</td>\n" % time)
+ f.write("<td></td>\n")
+
+ f.write("</tr>\n")
+ f.write("</table>\n")
+
+ f.write("<h2>Examples</h2>\n")
+ f.write("<table border=\"1\">\n")
+ f.write("<th> Result </th>\n")
+ f.write("<th>Example Name</th>\n")
+ for example in dom.getElementsByTagName("Example"):
+ f.write("<tr>\n")
+ result = get_node_text(example.getElementsByTagName("Result")[0])
+ if result == "FAIL":
+ f.write("<td style=\"color:red\">%s</td>\n" % result)
+ else:
+ f.write("<td style=\"color:green\">%s</td>\n" % result)
+ name = get_node_text(example.getElementsByTagName("Name")[0])
+ f.write("<td>%s</td>\n" % name)
+ f.write("</tr>\n")
+
+ f.write("</table>\n")
+
+ f.write("</body>\n")
+ f.write("</html>\n")
+ f.close()
+
+#
+# Python Control-C handling is broken in the presence of multiple threads.
+# Signals get delivered to the runnable/running thread by default and if
+# it is blocked, the signal is simply ignored. So we hook sigint and set
+# a global variable telling the system to shut down gracefully.
+#
+thread_exit = False
+
+def sigint_hook(signal, frame):
+ global thread_exit
+ thread_exit = True
+ return 0
+
+#
+# Waf can be configured to compile in debug or optimized modes. In each
+# case, the resulting built goes into a different directory. If we want
+# test tests to run from the correct code-base, we have to figure out which
+# mode waf is running in. This is called its active variant.
+#
+# XXX This function pokes around in the waf internal state file. To be a
+# little less hacky, we should add a commmand to waf to return this info
+# and use that result.
+#
+def read_waf_active_variant():
+ for line in open("build/c4che/default.cache.py").readlines():
+ if line.startswith("NS3_ACTIVE_VARIANT"):
+ exec(line, globals())
+ break
+
+ if options.verbose:
+ print "NS3_ACTIVE_VARIANT == %s" % NS3_ACTIVE_VARIANT
+
+#
+# In general, the build process itself naturally takes care of figuring out
+# which tests are built into the test runner. For example, if waf configure
+# determines that ENABLE_EMU is false due to some missing dependency,
+# the tests for the emu net device simply will not be built and will
+# therefore not be included in the built test runner.
+#
+# Examples, however, are a different story. In that case, we are just given
+# a list of examples that could be run. Instead of just failing, for example,
+# nsc-tcp-zoo if NSC is not present, we look into the waf saved configuration
+# for relevant configuration items.
+#
+# XXX This function pokes around in the waf internal state file. To be a
+# little less hacky, we should add a commmand to waf to return this info
+# and use that result.
+#
+def read_waf_config():
+ for line in open("build/c4che/%s.cache.py" % NS3_ACTIVE_VARIANT).readlines():
+ for item in interesting_config_items:
+ if line.startswith(item):
+ exec(line, globals())
+
+ if options.verbose:
+ for item in interesting_config_items:
+ print "%s ==" % item, eval(item)
+
+#
+# It seems pointless to fork a process to run waf to fork a process to run
+# the test runner, so we just run the test runner directly. The main thing
+# that waf would do for us would be to sort out the shared library path but
+# we can deal with that easily and do here.
+#
+# There can be many different ns-3 repositories on a system, and each has
+# its own shared libraries, so ns-3 doesn't hardcode a shared library search
+# path -- it is cooked up dynamically, so we do that too.
+#
+def make_library_path():
+ global LIBRARY_PATH
+
+ LIBRARY_PATH = "LD_LIBRARY_PATH='"
+
+ if sys.platform == "darwin":
+ LIBRARY_PATH = "DYLD_LIBRARY_PATH='"
+ elif sys.platform == "win32":
+ LIBRARY_PATH = "PATH='"
+ elif sys.platform == "cygwin":
+ LIBRARY_PATH = "PATH='"
+
+ for path in NS3_MODULE_PATH:
+ LIBRARY_PATH = LIBRARY_PATH + path + ":"
+
+ LIBRARY_PATH = LIBRARY_PATH + "'"
+
+def run_job_synchronously(shell_command, directory):
+ cmd = "%s %s/%s/%s" % (LIBRARY_PATH, NS3_BUILDDIR, NS3_ACTIVE_VARIANT, shell_command)
+ if options.verbose:
+ print "Synchronously execute %s" % cmd
+ proc = subprocess.Popen(cmd, shell=True, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout_results = proc.communicate()[0]
+ return (proc.returncode, stdout_results)
+
+#
+# This class defines a unit of testing work. It will typically refer to
+# a test suite to run using the test-runner, or an example to run directly.
+#
+class Job():
+ def __init__(self):
+ self.is_break = False
+ self.is_example = False
+ self.shell_command = ""
+ self.display_name = ""
+ self.cwd = ""
+ self.tmp_file_name = ""
+ self.returncode = False
+
+ #
+ # A job is either a standard job or a special job indicating that a worker
+ # thread should exist. This special job is indicated by setting is_break
+ # to true.
+ #
+ def set_is_break(self, is_break):
+ self.is_break = is_break
+
+ #
+ # Examples are treated differently than standard test suites. This is
+ # mostly because they are completely unaware that they are being run as
+ # tests. So we have to do some special case processing to make them look
+ # like tests.
+ #
+ def set_is_example(self, is_example):
+ self.is_example = is_example
+
+ #
+ # This is the shell command that will be executed in the job. For example,
+ #
+ # "utils/test-runner --suite=some-test-suite"
+ #
+ def set_shell_command(self, shell_command):
+ self.shell_command = shell_command
+
+ #
+ # This is the dispaly name of the job, typically the test suite or example
+ # name. For example,
+ #
+ # "some-test-suite" or "udp-echo"
+ #
+ def set_display_name(self, display_name):
+ self.display_name = display_name
+
+ #
+ # This is the base directory of the repository out of which the tests are
+ # being run. It will be used deep down in the testing framework to determine
+ # where the source directory of the test was, and therefore where to find
+ # provided test vectors. For example,
+ #
+ # "/home/user/repos/ns-3-dev"
+ #
+ def set_basedir(self, basedir):
+ self.basedir = basedir
+
+ #
+ # This is the current working directory that will be given to an executing
+ # test as it is being run. It will be used for examples to tell them where
+ # to write all of the pcap files that we will be carefully ignoring. For
+ # example,
+ #
+ # "/tmp/unchecked-traces"
+ #
+ def set_cwd(self, cwd):
+ self.cwd = cwd
+
+ #
+ # This is the temporary results file name that will be given to an executing
+ # test as it is being run. We will be running all of our tests in parallel
+ # so there must be multiple temporary output files. These will be collected
+ # into a single XML file at the end and then be deleted. The file names are
+ # just giant random numbers, for example
+ #
+ # "/tmp/testpy/5437925246732857"
+ #
+ def set_tmp_file_name(self, tmp_file_name):
+ self.tmp_file_name = tmp_file_name
+
+ #
+ # The return code received when the job process is executed.
+ #
+ def set_returncode(self, returncode):
+ self.returncode = returncode
+
+#
+# The worker thread class that handles the actual running of a given test.
+# Once spawned, it receives requests for work through its input_queue and
+# ships the results back through the output_queue.
+#
+class worker_thread(threading.Thread):
+ def __init__(self, input_queue, output_queue):
+ threading.Thread.__init__(self)
+ self.input_queue = input_queue
+ self.output_queue = output_queue
+
+ def run(self):
+ while True:
+ job = self.input_queue.get()
+ #
+ # Worker threads continue running until explicitly told to stop with
+ # a special job.
+ #
+ if job.is_break:
+ return
+ #
+ # If the global interrupt handler sets the thread_exit variable,
+ # we stop doing real work and just report back a "break" in the
+ # normal command processing has happened.
+ #
+ if thread_exit == True:
+ job.set_is_break(True)
+ self.output_queue.put(job)
+ continue
+ #
+ # Otherwise go about the business of running tests as normal.
+ #
+ else:
+ if options.verbose:
+ print "Launch %s" % job.shell_command
+
+ if job.is_example:
+ #
+ # If we have an example, the shell command is all we need to
+ # know. It will be something like "examples/udp-echo"
+ #
+ (job.returncode, standard_out) = run_job_synchronously(job.shell_command, job.cwd)
+ else:
+ #
+ # If we're a test suite, we need to provide a little more info
+ # to the test runner, specifically the base directory and temp
+ # file name
+ #
+ (job.returncode, standard_out) = run_job_synchronously(job.shell_command + " --basedir=%s --out=%s" %
+ (job.basedir, job.tmp_file_name), job.cwd)
+
+ if options.verbose:
+ print standard_out
+
+ self.output_queue.put(job)
+
+#
+# This is the main function that does the work of interacting with the test-runner
+# itself.
+#
+def run_tests():
+ #
+ # Run waf to make sure that everything is built, configured and ready to go
+ # unless we are explicitly told not to.
+ #
+ if options.nowaf == False:
+ proc = subprocess.Popen("./waf", shell=True)
+ proc.communicate()
+
+ #
+ # Pull some interesting configuration information out of waf, primarily
+ # so we can know where executables can be found, but also to tell us what
+ # pieces of the system have been built. This will tell us what examples
+ # are runnable.
+ #
+ read_waf_active_variant()
+ read_waf_config()
+ make_library_path()
+
+ #
+ # There are a couple of options that imply we can to exit before starting
+ # up a bunch of threads and running tests. Let's detect these cases and
+ # handle them without doing all of the hard work.
+ #
+ if options.kinds:
+ (rc, standard_out) = run_job_synchronously("utils/test-runner --kinds", os.getcwd())
+ print standard_out
+
+ if options.list:
+ (rc, standard_out) = run_job_synchronously("utils/test-runner --list", os.getcwd())
+ print standard_out
+
+ if options.kinds or options.list:
+ return
+
+ #
+ # We communicate results in two ways. First, a simple message relating
+ # PASS, FAIL, or SKIP is always written to the standard output. It is
+ # expected that this will be one of the main use cases. A developer can
+ # just run test.py with no options and see that all of the tests still
+ # pass.
+ #
+ # The second main use case is when detailed status is requested (with the
+ # --text or --html options). Typicall this will be text if a developer
+ # finds a problem, or HTML for nightly builds. In these cases, an
+ # XML file is written containing the status messages from the test suites.
+ # This file is then read and translated into text or HTML. It is expected
+ # that nobody will really be interested in the XML, so we write it to
+ # somewhere in /tmp with a random name to avoid collisions. Just in case
+ # some strange once-in-a-lifetime error occurs, we always write the info
+ # so it can be found, we just may not use it.
+ #
+ # When we run examples as smoke tests, they are going to want to create
+ # lots and lots of trace files. We aren't really interested in the contents
+ # of the trace files, so we also just stash them off in /tmp somewhere.
+ #
+ if not os.path.exists(TMP_OUTPUT_DIR):
+ os.makedirs(TMP_OUTPUT_DIR)
+
+ if not os.path.exists(TMP_TRACES_DIR):
+ os.makedirs(TMP_TRACES_DIR)
+
+ #
+ # Create the main output file and start filling it with XML. We need to
+ # do this since the tests will just append individual results to this file.
+ #
+ xml_results_file = TMP_OUTPUT_DIR + "%d.xml" % random.randint(0, sys.maxint)
+ f = open(xml_results_file, 'w')
+ f.write('<?xml version="1.0"?>\n')
+ f.write('<TestResults>\n')
+ f.close()
+
+ #
+ # We need to figure out what test suites to execute. We are either given one
+ # suite or example explicitly via the --suite or --example option, or we
+ # need to call into the test runner and ask it to list all of the available
+ # test suites. Further, we need to provide the constraint information if it
+ # has been given to us.
+ #
+ # This translates into allowing the following options with respect to the
+ # suites
+ #
+ # ./test,py: run all of the suites
+ # ./test.py --constrain=unit: run all unit suites
+ # ./test,py --suite=some-test-suite: run the single suite
+ # ./test,py --example=udp-echo: run no test suites
+ # ./test,py --suite=some-suite --example=some-example: run the single suite
+ #
+ # We can also use the --constrain option to provide an ordering of test
+ # execution quite easily.
+ #
+ if len(options.suite):
+ suites = options.suite + "\n"
+ elif len(options.example) == 0:
+ if len(options.constrain):
+ (rc, suites) = run_job_synchronously("utils/test-runner --list --constrain=%s" % options.constrain, os.getcwd())
+ else:
+ (rc, suites) = run_job_synchronously("utils/test-runner --list", os.getcwd())
+ else:
+ suites = ""
+
+ #
+ # suite_list will either a single test suite name that the user has
+ # indicated she wants to run or a list of test suites provided by
+ # the test-runner possibly according to user provided constraints.
+ # We go through the trouble of setting up the parallel execution
+ # even in the case of a single suite to avoid having two process the
+ # results in two different places.
+ #
+ suite_list = suites.split('\n')
+
+ #
+ # We now have a possibly large number of test suites to run, so we want to
+ # run them in parallel. We're going to spin up a number of worker threads
+ # that will run our test jobs for us.
+ #
+ # XXX Need to figure out number of CPUs without the multiprocessing
+ # dependency since multiprocessing is not standard `till Python 2.6
+ #
+ input_queue = Queue.Queue(0)
+ output_queue = Queue.Queue(0)
+
+ jobs = 0
+ threads=[]
+
+ processors = multiprocessing.cpu_count()
+ for i in range(processors):
+ thread = worker_thread(input_queue, output_queue)
+ threads.append(thread)
+ thread.start()
+
+ #
+ # We now have worker threads spun up, and a list of work to do. So, run
+ # through the list of test suites and dispatch a job to run each one.
+ #
+ # Dispatching will run with unlimited speed and the worker threads will
+ # execute as fast as possible from the queue.
+ #
+ for test in suite_list:
+ if len(test):
+ job = Job()
+ job.set_is_example(False)
+ job.set_display_name(test)
+ job.set_tmp_file_name(TMP_OUTPUT_DIR + "%d" % random.randint(0, sys.maxint))
+ job.set_cwd(os.getcwd())
+ job.set_basedir(os.getcwd())
+ job.set_shell_command("utils/test-runner --suite='%s'" % test)
+
+ if options.verbose:
+ print "Queue %s" % test
+
+ input_queue.put(job)
+ jobs = jobs + 1
+
+ #
+ # We've taken care of the discovered or specified test suites. Now we
+ # have to deal with examples run as smoke tests. We have a list of all of
+ # the example programs it makes sense to try and run. Each example will
+ # have a condition associated with it that must evaluate to true for us
+ # to try and execute it. This is used to determine if the example has
+ # a dependency that is not satisfied. For example, if an example depends
+ # on NSC being configured by waf, that example should have a condition
+ # that evaluates to true if NSC is enabled. For example,
+ #
+ # ("tcp-nsc-zoo", "ENABLE_NSC == True"),
+ #
+ # In this case, the example "tcp-nsc-zoo" will only be run if we find the
+ # waf configuration variable "ENABLE_NSC" to be True.
+ #
+ # We don't care at all how the trace files come out, so we just write them
+ # to a single temporary directory.
+ #
+ # XXX As it stands, all of the trace files have unique names, and so file
+ # collisions can only happen if two instances of an example are running in
+ # two versions of the test.py process concurrently. We may want to create
+ # uniquely named temporary traces directories to avoid this problem.
+ #
+ # We need to figure out what examples to execute. We are either given one
+ # suite or example explicitly via the --suite or --example option, or we
+ # need to walk the list of examples looking for available example
+ # conditions.
+ #
+ # This translates into allowing the following options with respect to the
+ # suites
+ #
+ # ./test,py: run all of the examples
+ # ./test.py --constrain=unit run no examples
+ # ./test.py --constrain=example run all of the examples
+ # ./test,py --suite=some-test-suite: run no examples
+ # ./test,py --example=some-example: run the single example
+ # ./test,py --suite=some-suite --example=some-example: run the single example
+ #
+ # XXX could use constrain to separate out examples used for performance
+ # testing
+ #
+ if len(options.suite) == 0 and len(options.example) == 0:
+ if len(options.constrain) == 0 or options.constrain == "example":
+ for test, condition in example_tests:
+ if eval(condition) == True:
+ job = Job()
+ job.set_is_example(True)
+ job.set_display_name(test)
+ job.set_tmp_file_name("")
+ job.set_cwd(TMP_TRACES_DIR)
+ job.set_basedir(os.getcwd())
+ job.set_shell_command("examples/%s" % test)
+
+ if options.verbose:
+ print "Queue %s" % test
+
+ input_queue.put(job)
+ jobs = jobs + 1
+ elif len(options.example):
+ #
+ # If you tell me to run an example, I will try and run the example
+ # irrespective of any condition.
+ #
+ job = Job()
+ job.set_is_example(True)
+ job.set_display_name(options.example)
+ job.set_tmp_file_name("")
+ job.set_cwd(TMP_TRACES_DIR)
+ job.set_basedir(os.getcwd())
+ job.set_shell_command("examples/%s" % options.example)
+
+ if options.verbose:
+ print "Queue %s" % test
+
+ input_queue.put(job)
+ jobs = jobs + 1
+
+ #
+ # Tell the worker threads to pack up and go home for the day. Each one
+ # will exit when they see their is_break task.
+ #
+ for i in range(processors):
+ job = Job()
+ job.set_is_break(True)
+ input_queue.put(job)
+
+ #
+ # Now all of the tests have been dispatched, so all we have to do here
+ # in the main thread is to wait for them to complete. Keyboard interrupt
+ # handling is broken as mentioned above. We use a signal handler to catch
+ # sigint and set a global variable. When the worker threads sense this
+ # they stop doing real work and will just start throwing jobs back at us
+ # with is_break set to True. In this case, there are no real results so we
+ # ignore them. If there are real results, we always print PASS or FAIL to
+ # standard out as a quick indication of what happened.
+ #
+ for i in range(jobs):
+ job = output_queue.get()
+ if job.is_break:
+ continue
+
+ if job.is_example:
+ kind = "Example"
+ else:
+ kind = "TestSuite"
+
+ if job.returncode == 0:
+ status = "PASS"
+ else:
+ status = "FAIL"
+
+ print "%s: %s %s" % (status, kind, job.display_name)
+
+ if job.is_example == True:
+ #
+ # Examples are the odd man out here. They are written without any
+ # knowledge that they are going to be run as a test, so we need to
+ # cook up some kind of output for them. We're writing an xml file,
+ # so we do some simple XML that says we ran the example.
+ #
+ # XXX We could add some timing information to the examples, i.e. run
+ # them through time and print the results here.
+ #
+ f = open(xml_results_file, 'a')
+ f.write('<Example>\n')
+ example_name = " <Name>%s</Name>\n" % job.display_name
+ f.write(example_name)
+ if job.returncode == 0:
+ f.write(' <Result>PASS</Result>\n')
+ elif job.returncode == 1:
+ f.write(' <Result>FAIL</Result>\n')
+ else:
+ f.write(' <Result>CRASH</Result>\n')
+
+ f.write('</Example>\n')
+ f.close()
+ else:
+ #
+ # If we're not running an example, we're running a test suite.
+ # These puppies are running concurrently and generating output
+ # that was written to a temporary file to avoid collisions.
+ #
+ # Now that we are executing sequentially in the main thread, we can
+ # concatenate the contents of the associated temp file to the main
+ # results file and remove that temp file.
+ #
+ # One thing to consider is that a test suite can crash just as
+ # well as any other program, so we need to deal with that
+ # possibility as well. If it ran correctly it will return 0
+ # if it passed, or 1 if it failed. In this case, we can count
+ # on the results file it saved being complete. If it crashed, it
+ # will return some other code, and the file should be considered
+ # corrupt and useless. If the suite didn't create any XML, then
+ # we're going to have to do it ourselves.
+ #
+ if job.returncode == 0 or job.returncode == 1:
+ f_to = open(xml_results_file, 'a')
+ f_from = open(job.tmp_file_name, 'r')
+ f_to.write(f_from.read())
+ f_to.close()
+ f_from.close()
+ else:
+ f = open(xml_results_file, 'a')
+ f.write("<TestSuite>\n")
+ f.write(" <Name>%s</Name>\n" % job.display_name)
+ f.write(' <Result>CRASH</Result>\n')
+ f.write("</TestSuite>\n")
+ f.close()
+
+ os.remove(job.tmp_file_name)
+
+ #
+ # We have all of the tests run and the results written out. One final
+ # bit of housekeeping is to wait for all of the threads to close down
+ # so we can exit gracefully.
+ #
+ for thread in threads:
+ thread.join()
+
+ #
+ # Back at the beginning of time, we started the body of an XML document
+ # since the test suites and examples were going to just write their
+ # individual pieces. So, we need to finish off and close out the XML
+ # document
+ #
+ f = open(xml_results_file, 'a')
+ f.write('</TestResults>\n')
+ f.close()
+
+ #
+ # The last things to do are to translate the XML results file to "human
+ # readable form" if the user asked for it
+ #
+ if len(options.html):
+ translate_to_html(xml_results_file, options.html)
+
+ if len(options.text):
+ translate_to_text(xml_results_file, options.text)
+
+def main(argv):
+ random.seed()
+
+ parser = optparse.OptionParser()
+ parser.add_option("-c", "--constrain", action="store", type="string", dest="constrain", default="",
+ metavar="KIND",
+ help="constrain the test-runner by kind of test")
+
+ parser.add_option("-e", "--example", action="store", type="string", dest="example", default="",
+ metavar="EXAMPLE",
+ help="specify a single example to run")
+
+ parser.add_option("-k", "--kinds", action="store_true", dest="kinds", default=False,
+ help="print the kinds of tests available")
+
+ parser.add_option("-l", "--list", action="store_true", dest="list", default=False,
+ help="print the list of known tests")
+
+ parser.add_option("-n", "--nowaf", action="store_true", dest="nowaf", default=False,
+ help="do not run waf before starting testing")
+
+ parser.add_option("-s", "--suite", action="store", type="string", dest="suite", default="",
+ metavar="TEST-SUITE",
+ help="specify a single test suite to run")
+
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="print progress and informational messages")
+
+ parser.add_option("-w", "--web", "--html", action="store", type="string", dest="html", default="",
+ metavar="HTML-FILE",
+ help="write detailed test results into HTML-FILE.html")
+
+ parser.add_option("-t", "--text", action="store", type="string", dest="text", default="",
+ metavar="TEXT-FILE",
+ help="write detailed test results into TEXT-FILE.txt")
+
+ global options
+ options = parser.parse_args()[0]
+ signal.signal(signal.SIGINT, sigint_hook)
+ run_tests()
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/utils/test-runner.cc Sat Sep 12 19:44:17 2009 -0700
@@ -0,0 +1,248 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2009 University of Washington
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ns3/test.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+
+using namespace ns3;
+
+//
+// Run one of the test suites. Returns an integer with the boolean sense of
+// "an error has occurred." That is, 0 == false -> no error; 1 == true -> an
+// error occurred.
+//
+int
+main (int argc, char *argv[])
+{
+ bool doVerbose = false;
+ bool doList = false;
+ bool doHelp = false;
+ bool doSuite = false;
+ bool doKinds = false;
+
+ bool haveBasedir = false;
+ bool haveOutfile = false;
+ bool haveType = false;
+
+ std::string suiteName;
+ std::string basedir;
+ std::string outfileName;
+ std::string typeName;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string arg(argv[i]);
+
+ if (arg.find ("--basedir=") != std::string::npos)
+ {
+ basedir = arg.substr (arg.find_first_of ("=") + 1, 9999);
+ haveBasedir = true;
+ }
+
+ if (arg.find ("--constrain=") != std::string::npos)
+ {
+ typeName = arg.substr (arg.find_first_of ("=") + 1, 9999);
+ haveType = true;
+ }
+
+ if (arg.compare ("--help") == 0)
+ {
+ doHelp = true;
+ }
+
+ if (arg.compare ("--kinds") == 0)
+ {
+ doKinds = true;
+ }
+
+ if (arg.compare ("--list") == 0)
+ {
+ doList = true;
+ }
+
+ if (arg.find ("--out=") != std::string::npos)
+ {
+ outfileName = arg.substr (arg.find_first_of ("=") + 1, 9999);
+ haveOutfile = true;
+ }
+
+ if (arg.find ("--suite=") != std::string::npos)
+ {
+ suiteName = arg.substr (arg.find_first_of ("=") + 1, 9999);
+ doSuite = true;
+ }
+
+ if (arg.compare ("--verbose") == 0)
+ {
+ doVerbose = true;
+ }
+ }
+
+ //
+ // A help request trumps everything else. If we have one, just print the help
+ // and leave.
+ //
+ if (doHelp)
+ {
+ std::cout << " --basedir=dir: Set the base directory (where to find src) to \"dir\"" << std::endl;
+ std::cout << " --constrain=test-type: Constrain checks to test suites of type \"test-type\"" << std::endl;
+ std::cout << " --help: Print this message" << std::endl;
+ std::cout << " --kinds: List all of the available kinds of tests" << std::endl;
+ std::cout << " --list: List all of the test suites (optionally constrained by test-type)" << std::endl;
+ std::cout << " --out=file-name: Set the test status output file to \"file-name\"" << std::endl;
+ std::cout << " --suite=suite-name: Run the test suite named \"suite-name\"" << std::endl;
+ std::cout << " --verbose: Turn on messages in the run test suites" << std::endl;
+
+ return false;
+ }
+
+ //
+ // A kinds request trumps everything remaining. If we are asked, just
+ // print the list of types and leave.
+ //
+ if (doKinds)
+ {
+ //
+ // Coming up with a string to represent a test type is completely up to
+ // us here. We just define the types as being a string composed of the
+ // enum defined in test.h converted to lower case.
+ //
+ std::cout << " bvt: Build Verification Tests (to see if build completed successfully)" << std::endl;
+ std::cout << " unit: Unit Tests (within modules to check basic functionality)" << std::endl;
+ std::cout << " system: System Tests (spans modules to check integration of modules)" << std::endl;
+ std::cout << " example: Examples (to see if example programs run successfully)" << std::endl;
+ std::cout << " performance: Performance Tests (check to see if the system is as fast as expected)" << std::endl;
+
+ return false;
+ }
+
+ //
+ // A list request is the first functional request. It trumps running the
+ // actual tests. If we get a list request, we don't run anything, we just
+ // do the requested list which may or may not be qualified by a typename.
+ //
+ if (doList)
+ {
+ for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
+ {
+ TestSuite *suite = TestRunner::GetTestSuite (i);
+
+ //
+ // Filter the tests listed by type if requested.
+ //
+ if (haveType)
+ {
+ TestSuite::TestType type = suite->GetTestType ();
+ if (typeName == "bvt" && type != TestSuite::BVT)
+ {
+ continue;
+ }
+
+ if (typeName == "unit" && type != TestSuite::UNIT)
+ {
+ continue;
+ }
+
+ if (typeName == "system" && type != TestSuite::SYSTEM)
+ {
+ continue;
+ }
+
+ if (typeName == "example" && type != TestSuite::EXAMPLE)
+ {
+ continue;
+ }
+
+ if (typeName == "performance" && type != TestSuite::PERFORMANCE)
+ {
+ continue;
+ }
+ }
+
+ //
+ // This creates a list of test suite names that can be used by the
+ // high level test manager to get a list of all tests. It will then
+ // typically launch individual tests in parallel, calling back here
+ // with a specific "suite=" to run.
+ //
+ std::cout << suite->GetName () << std::endl;
+ }
+
+ return false;
+ }
+
+ //
+ // If we haven't been asked to run a test suite, we are just going to happily
+ // try and run everything. Test suites are possibly going to need to figure
+ // out where there source directory is, and to do that they will need to know
+ // where the base directory of the distribution is (the directory in which
+ // "src" is found). We could try and run without it, but when it is needed,
+ // the test will fail with an assertion. So to be safe, we require a basedir
+ // to proceed.
+ //
+
+ if (haveBasedir == false)
+ {
+ std::cout << "Must specify a base directory to run tests (use --basedir option)" << std::endl;
+ return true;
+ }
+
+ //
+ // If given an output file, we just append the output of each test suite
+ // we're asked to run to the end of that file. We need to append since the
+ // higher level test runner may be just running a number of tests back to
+ // back. We leave it up to that code to decide how to deal with possible
+ // parallel operation -- we just append to a file here. If no output file
+ // is specified, we don't do any output and just return the sense of error
+ // given by the test.
+ //
+ std::ofstream *pofs = 0;
+ std::ofstream ofs;
+
+ if (!outfileName.empty ())
+ {
+ ofs.open (outfileName.c_str (), std::fstream::out | std::fstream::app);
+ pofs = &ofs;
+ }
+
+ //
+ // If we have a specified test suite to run, then we only run that suite.
+ // The default case is to "run everything. We don't expect this to be done
+ // much since typically higher level code will be running suites in parallel
+ // but we'll do it if asked.
+ //
+ bool result = false;
+
+ for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
+ {
+ TestSuite *testSuite = TestRunner::GetTestSuite (i);
+ if (doSuite == false || (doSuite == true && suiteName == testSuite->GetName ()))
+ {
+ testSuite->SetBaseDir (basedir);
+ testSuite->SetStream (pofs);
+ testSuite->SetVerbose (doVerbose);
+ result |= testSuite->Run ();
+ }
+ }
+
+ ofs.close();
+ return result;
+}
--- a/utils/wscript Thu Sep 10 11:41:33 2009 +0100
+++ b/utils/wscript Sat Sep 12 19:44:17 2009 -0700
@@ -9,6 +9,11 @@
unit_tests.source = 'run-tests.cc'
## link unit test program with all ns3 modules
unit_tests.uselib_local = 'ns3'
+
+ test_runner = bld.create_ns3_program('test-runner', ['core'])
+ test_runner.install_path = None # do not install
+ test_runner.source = 'test-runner.cc'
+ test_runner.uselib_local = 'ns3'
obj = bld.create_ns3_program('bench-simulator', ['simulator'])
obj.source = 'bench-simulator.cc'