From d6ea34c943033c2ad6c66aef77c2cfce44ca68e9 Mon Sep 17 00:00:00 2001 From: testkit Date: Tue, 8 May 2012 18:03:15 +0800 Subject: [PATCH] testkit-lite 1.0 --- ChangeLog | 38 ++ LICENSE | 340 +++++++++++++++ MANIFEST | 54 +++ MANIFEST.in | 11 + README | 142 ++++++ README.md | 2 - TODO | 5 + __init__.py | 0 autoexec.py | 107 +++++ basicstruct.py | 106 +++++ fakeinstall | 23 + man/testkit-lite.1 | 142 ++++++ manexec.py | 44 ++ postinstall | 21 + runner.py | 478 +++++++++++++++++++++ setup.cfg | 33 ++ setup.py | 47 ++ str2.py | 68 +++ testfilter.py | 217 ++++++++++ testkit-lite | 225 ++++++++++ testparser.py | 189 ++++++++ textreport.py | 97 +++++ tree.py | 450 +++++++++++++++++++ unit.py | 361 ++++++++++++++++ unittest/autoexec_unittest.py | 64 +++ unittest/basicstruct_unittest.py | 158 +++++++ unittest/complex.xml | 70 +++ unittest/informal.xml | 39 ++ unittest/manexec | 31 ++ unittest/manexec.sh | 25 ++ unittest/manexec_unittest.py | 49 +++ unittest/manual.xml | 20 + unittest/runauto.py | 38 ++ unittest/runmanual.py | 42 ++ unittest/runner_unittest.py | 91 ++++ unittest/simple.xml | 54 +++ ...onglonglonglonglonglonglonglonglonglonglong.xml | 55 +++ unittest/str2_unittest.py | 60 +++ unittest/testfilter_unittest.py | 271 ++++++++++++ unittest/testparser_unittest.py | 77 ++++ unittest/textreport_unittest.py | 68 +++ unittest/tree_test_graph/test1PrintTree.graph | 43 ++ unittest/tree_test_graph/test2SetNodeKey.graph | 43 ++ unittest/tree_test_graph/test3SetNodeShow.graph | 43 ++ .../test4PrintNodeTreeReverse.graph | 6 + unittest/tree_test_graph/test5DelNode.graph | 14 + unittest/tree_test_graph/test6UpdateNode.graph | 43 ++ unittest/tree_test_graph/test7PrintNodeTree.graph | 21 + unittest/tree_unittest.py | 147 +++++++ unittest/unit_unittest.py | 346 +++++++++++++++ unittest/validate_unittest.py | 50 +++ unittest/xmlreport_unittest.py | 95 ++++ validate.py | 38 ++ xmlreport.py | 350 +++++++++++++++ xsd/testdefinition-results.xsd | 292 +++++++++++++ xsd/testdefinition-syntax.xsd | 207 +++++++++ 56 files changed, 6148 insertions(+), 2 deletions(-) create mode 100644 ChangeLog create mode 100644 LICENSE create mode 100644 MANIFEST create mode 100644 MANIFEST.in create mode 100644 README delete mode 100644 README.md create mode 100644 TODO create mode 100644 __init__.py create mode 100644 autoexec.py create mode 100644 basicstruct.py create mode 100644 fakeinstall create mode 100644 man/testkit-lite.1 create mode 100644 manexec.py create mode 100644 postinstall create mode 100644 runner.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 str2.py create mode 100644 testfilter.py create mode 100644 testkit-lite create mode 100644 testparser.py create mode 100644 textreport.py create mode 100644 tree.py create mode 100644 unit.py create mode 100644 unittest/autoexec_unittest.py create mode 100644 unittest/basicstruct_unittest.py create mode 100644 unittest/complex.xml create mode 100644 unittest/informal.xml create mode 100644 unittest/manexec create mode 100644 unittest/manexec.sh create mode 100644 unittest/manexec_unittest.py create mode 100644 unittest/manual.xml create mode 100644 unittest/runauto.py create mode 100644 unittest/runmanual.py create mode 100644 unittest/runner_unittest.py create mode 100644 unittest/simple.xml create mode 100644 unittest/simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml create mode 100644 unittest/str2_unittest.py create mode 100644 unittest/testfilter_unittest.py create mode 100644 unittest/testparser_unittest.py create mode 100644 unittest/textreport_unittest.py create mode 100644 unittest/tree_test_graph/test1PrintTree.graph create mode 100644 unittest/tree_test_graph/test2SetNodeKey.graph create mode 100644 unittest/tree_test_graph/test3SetNodeShow.graph create mode 100644 unittest/tree_test_graph/test4PrintNodeTreeReverse.graph create mode 100644 unittest/tree_test_graph/test5DelNode.graph create mode 100644 unittest/tree_test_graph/test6UpdateNode.graph create mode 100644 unittest/tree_test_graph/test7PrintNodeTree.graph create mode 100644 unittest/tree_unittest.py create mode 100644 unittest/unit_unittest.py create mode 100644 unittest/validate_unittest.py create mode 100644 unittest/xmlreport_unittest.py create mode 100644 validate.py create mode 100644 xmlreport.py create mode 100644 xsd/testdefinition-results.xsd create mode 100644 xsd/testdefinition-syntax.xsd diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..8a09713 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,38 @@ +testkit-lite 1.0.5: +=================== +* Tue Jul 27 2011 Wang, Jing 1.0.5-1 + - Refresh test-definition schema + - Add support for latest test-definition schema + +* Tue Jul 14 2011 Wang, Jing 1.0.4-1 + - add -o output option, let user specify result file location + - add external reporter plugin support + +* Sat Apr 23 2011 Wei, Zhang 1.0.3-1 + - add separate log for each case + +* Fri Nov 12 2010 Wei, Zhang 1.0.2-1 + - refresh the schema file from http://gitorious.org/qa-tools/test-definition/ + - use explicit step to replace pseudo step for descrption + +* Wed Sep 27 2010 Wei, Zhang 1.0.1-1 + - add option -T to strip those non-executed cases in result xml + - not print empty presteps or poststeps in result xml + +* Wed Jul 21 2010 Wei, Zhang 1.0.0-1 + - for 1.0.0 release + +* Mon Jul 19 2010 Wei, Zhang 0.6.1-3 + - add normal user running support for command testkit-lite + - deal with non-params as -h + +* Wed Jul 14 2010 Wei, Zhang 0.6.1-2 + - install directory changes from /opt/testkit-lite to /opt/testkit/lite + - encode to utf8 when xml content is transfered to unicode object by lxml + +* Fri Jul 09 2010 Wei, Zhang 0.6.1-1 + - pre_steps and post_steps are forced automated. (align with nokia's testrunner) + - deal with manual tests which has description element, it's the requirement of tests.xml exported from testlink. + - imporve testparser to handle unicode object from tests.xml + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6d45519 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..9edcd16 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,54 @@ +ChangeLog +LICENSE +MANIFEST.in +README +TODO +fakeinstall +postinstall +setup.cfg +setup.py +testkit-lite +./__init__.py +./autoexec.py +./basicstruct.py +./manexec.py +./runner.py +./str2.py +./testfilter.py +./testparser.py +./textreport.py +./tree.py +./unit.py +./validate.py +./xmlreport.py +man/testkit-lite.1 +unittest/autoexec_unittest.py +unittest/basicstruct_unittest.py +unittest/complex.xml +unittest/informal.xml +unittest/manexec +unittest/manexec.sh +unittest/manexec_unittest.py +unittest/manual.xml +unittest/runauto.py +unittest/runmanual.py +unittest/runner_unittest.py +unittest/simple.xml +unittest/simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml +unittest/str2_unittest.py +unittest/testfilter_unittest.py +unittest/testparser_unittest.py +unittest/textreport_unittest.py +unittest/tree_unittest.py +unittest/unit_unittest.py +unittest/validate_unittest.py +unittest/xmlreport_unittest.py +unittest/tree_test_graph/test1PrintTree.graph +unittest/tree_test_graph/test2SetNodeKey.graph +unittest/tree_test_graph/test3SetNodeShow.graph +unittest/tree_test_graph/test4PrintNodeTreeReverse.graph +unittest/tree_test_graph/test5DelNode.graph +unittest/tree_test_graph/test6UpdateNode.graph +unittest/tree_test_graph/test7PrintNodeTree.graph +xsd/testdefinition-results.xsd +xsd/testdefinition-syntax.xsd diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..aba0cb6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include xsd/* +include man/* +include unittest/* +include unittest/tree_test_graph/* +include LICENSE +include README +include ChangeLog +include MANIFEST.in +include TODO +include fakeinstall +include postinstall diff --git a/README b/README new file mode 100644 index 0000000..3086dcf --- /dev/null +++ b/README @@ -0,0 +1,142 @@ + Quick Start: + ================= + + At first, prepare one tests.xml file aligned with schema files: /opt/testkit/lite/xsd/testdefinition-syntax.xsd. + + And then, + + 1) You can simply conduct one or more tests.xml: + testkit-lite -f /tests.xml /tests.xml + + 2) You can also arrange several tests.xml to one testsxmlconfig, and run + testkit-lite --testxmlconfig /testsxmlconfig + Also, it supports multi testsxmlconfig: + testkit-lite --testxmlconfig /testsxmlconfig /testsxmlconfig + The testsxmlconfig is like this: + ############################## + # /opt/browser/tests.xml # + # /opt/ofono/tests.xml # + # /opt/gfx/tests.xml # + # /opt/pulseaudio/tests.xml # + ############################## + + 3) If you want to validate one new tests.xml: + testkit-lite -f tests.xml -V + + 4) If you just want to get the statistic (such as the testcases number or the structure), dryrun could help: + testkit-lite -f tests.xml -D + + 5) If you want to execute auto tests: + testkit-lite -f tests.xml + + 6) If you just want to execute manual tests: + testkit-lite -f tests.xml -M + + 7) If you want to execute all the auto and manual tests: + testkit-lite -f tests.xml -A + + 8) If you just want to execute the significant tests: + testkit-lite -f tests.xml -S + + 9) If you want to choose some filters: + testkit-lite -f tests1.xml tests2.xml --level level1 level2 --type type1 type2 --Ntestcase gfx1 gfx2... + Note that the test cases will be filtered out by both black rules and white rules. + + 10) At last, you can freely compose the above paramters together: + testkit-lite -f tests1.xml tests2.xml --testxmlconfig config1 config2 -D -A -S -C --level level1 level2 --type type1 type2 ... + + 11) One more parameter is prepared for customized compatible mode for test report: + testkit-lite -f tests.xml -C + + + Get Results: + ================= + Two kinds of test report will be generated: + 1) .textresult + example: + ===================================TestReport=================================== + TYPE PASS FAIL N/A + ---/usr/share/mnts1.0-distromisc-test/tests.xml XML 0 0 24 + `---Distro Misc SUITE 0 0 24 + |---General shortkeys SET 0 0 2 + | |---general_shortkeys_console_switch CASE 0 0 1 + | `---general_shortkeys_standard_shortkeys CASE 0 0 1 + |---Linux Usability SET 0 0 4 + | |---linux_usability_common_commands CASE 0 0 1 + | |---linux_usability_default_app_and_MIME CASE 0 0 1 + | |---linux_usability_gconf_database CASE 0 0 1 + | `---linux_usability_security_usability CASE 0 0 1 + |---Log system SET 0 0 4 + | |---log_system_UI_process CASE 0 0 1 + | |---log_system_monitor_log_access CASE 0 0 1 + | |---log_system_process_pulseaudio_daemon CASE 0 0 1 + | `---log_system_sreadhead CASE 0 0 1 + |---Non-defult integrated applications SET 0 0 3 + | |---non_default_apps_installation CASE 0 0 1 + | |---non_default_apps_sanity_check CASE 0 0 1 + | `---non_default_apps_uninstallation CASE 0 0 1 + |---Package check SET 0 0 3 + | |---package_check_core_debuginfo CASE 0 0 1 + | |---package_check_core_dependency CASE 0 0 1 + | `---package_check_core_version CASE 0 0 1 + |---Peripheral Devices SET 0 0 6 + | |---peripheral_devices_automount CASE 0 0 1 + | |---peripheral_devices_external_bluetooth CASE 0 0 1 + | |---peripheral_devices_external_cdrom CASE 0 0 1 + | |---peripheral_devices_usb_hotplug CASE 0 0 1 + | |---peripheral_devices_usb_hub CASE 0 0 1 + | `---peripheral_devices_usb_mount_umount CASE 0 0 1 + `---User actions on the stage of system boot-up SET 0 0 2 + |---user_actions_at_bootup_keyboard_operations CASE 0 0 1 + `---user_actions_at_bootup_mouse_operations CASE 0 0 1 + + 2) .xmlresult + xml result files aligned with schema files: /opt/testkit/lite/xsd/testdefinition-results.xsd + example: + + The result will be under /opt/testkit/lite/latest after execution, you can also check the history results in /opt/testkit/lite/yyyy-mm-dd-HH:MM:SS.NNNNNN. + + + Notes: + ================= + testkit-lite's TestLog is stored to /opt/testkit/lite/latest + testkit-lite enables only automatic tests by default + Obviously -A and -M are conflict options. + + + Detail Options: + ================= + Options: + -f, --testxmls Specify the test.xml, support multi test.xml + Refer to --testxmlconfig to add by filelist + --testxmlconfig Specify the file listing group of testxmls + Support multi testxmlconfig files + -D, --dryrun Dry-run the selected test cases + -V, --validate-only Do only input xml validation, do not execute tests + -M, --manual-only Enable only manual tests to be executed + -A, --all Enable both automatic and manual tests + -S, --significant-only + Enable only significant tests to be executed + -C, --compatibleresultxml + use nokia compatible result xml format + --domain Select the specified white-rules filter: domain + --requirement Select the specified white-rules filter: requirement + --testsuite Select the specified white-rules filter: testsuite + --testset Select the specified white-rules filter: testset + --environment Select the specified white-rules filter: environment + --level Select the specified white-rules filter: level + --feature Select the specified white-rules filter: feature + --testcase Select the specified white-rules filter: testcase + --subfeature Select the specified white-rules filter: subfeature + --type Select the specified white-rules filter: type + --Ndomain Select the specified black-rules filter: domain + --Nrequirement Select the specified black-rules filter: requirement + --Ntestsuite Select the specified black-rules filter: testsuite + --Ntestset Select the specified black-rules filter: testset + --Nenvironment Select the specified black-rules filter: environment + --Nlevel Select the specified black-rules filter: level + --Nfeature Select the specified black-rules filter: feature + --Ntestcase Select the specified black-rules filter: testcase + --Nsubfeature Select the specified black-rules filter: subfeature + --Ntype Select the specified black-rules filter: type + -h, --help show this help message and exit diff --git a/README.md b/README.md deleted file mode 100644 index dad01d9..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -testkit-lite -============ \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..6423a35 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +TODO: +======== +1. add --verbose and logging level. +2. support html view when execute manual test + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/autoexec.py b/autoexec.py new file mode 100644 index 0000000..4c13ef7 --- /dev/null +++ b/autoexec.py @@ -0,0 +1,107 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +import os +import sys +import time +import threading +import subprocess +from multiprocessing import Process +from multiprocessing import Value + + +############################################################################### +def shell_exec(cmd, timeout=None, boutput=False): + + """shell executor, return [exitcode, stdout/stderr] + timeout: None means unlimited timeout + boutput: specify whether print output during the command running + """ + BUFFILE1 = os.path.expanduser("~") + "/.shellexec_buffile_stdout" + BUFFILE2 = os.path.expanduser("~") + "/.shellexec_buffile_stderr" + + LOOP_DELTA = 0.01 + + exit_code = None + stdout_log = "" + stderr_log = "" + + wbuffile1 = file(BUFFILE1, "w") + wbuffile2 = file(BUFFILE2, "w") + rbuffile1 = file(BUFFILE1, "r") + rbuffile2 = file(BUFFILE2, "r") + + # start execution process + cmdPopen = subprocess.Popen(args=cmd, shell=True, close_fds=True, + stdout=wbuffile1, stderr=wbuffile2) + + def print_log(): + sys.stdout.write(rbuffile1.read()) + sys.stdout.write(rbuffile2.read()) + sys.stdout.flush() + + # loop for timeout and print + rbuffile1.seek(0) + rbuffile2.seek(0) + t = timeout + while True: + + exit_code = cmdPopen.poll() + if exit_code is not None: + break + + if boutput: + print_log() + + if t is not None: + if t <= 0: + # timeout, kill command + try: + cmdPopen.kill() + except: + pass + break + else: + t -= LOOP_DELTA + + time.sleep(LOOP_DELTA) + + + # print left output + if boutput: + # flush left output in log + print_log() + + # store the log from buffile + rbuffile1.seek(0) + rbuffile2.seek(0) + stdout_log = rbuffile1.read() + stderr_log = rbuffile2.read() + + # close file + wbuffile1.close() + wbuffile2.close() + rbuffile1.close() + rbuffile2.close() + os.remove(BUFFILE1) + os.remove(BUFFILE2) + + return [exit_code, stdout_log.strip('\n'), stderr_log.strip('\n')] diff --git a/basicstruct.py b/basicstruct.py new file mode 100644 index 0000000..49ba857 --- /dev/null +++ b/basicstruct.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# define some data structure here +# + + +############################################################################### +class LimitedAttributes(object): + """the limited attributes class + + LimitedAttributes is limited in following 4 aspects: + 1. LIMITED_NAMES list defines the attributes need to be limited + 2. LIMITED_DEFAULTS dict defines the default value + 3. LIMITED_TYPES dict defines the type limitation + 4. LIMITED_VALUES dict defines the value limitation + + Besides these LIMITED attributes, the sub-class could have other + attributes defined in common way + """ + + # overwrite following variables in sub-class + LIMITED_NAMES = [] + LIMITED_DEFAULTS = {} + LIMITED_TYPES = {} + LIMITED_VALUES = {} + + def __setattr__(self, name, value): + """limit the attribute's type/name/value + """ + + if name in self.LIMITED_NAMES: + if value is not None: + + # TYPE limitation + if name in self.LIMITED_TYPES: + if None != self.LIMITED_TYPES[name] and \ + 0 != self.LIMITED_TYPES[name] and \ + type(value) not in self.LIMITED_TYPES[name]: + print "attribute *%s*'s value *%s* is not in limited_type scope: *%s*, set to None" \ + %(name, value, self.LIMITED_TYPES[name]) + value = None + + # VALUE limitation + if name in self.LIMITED_VALUES: + if None != self.LIMITED_VALUES[name] and \ + 0 != self.LIMITED_VALUES[name] and \ + value not in self.LIMITED_VALUES[name]: + print "attribute *%s*'s value *%s* is not in limited_value scope: *%s*, set to None" \ + %(name, value, self.LIMITED_VALUES[name]) + value = None + + object.__setattr__(self, name, value) + + + def __getattr__(self, name): + """just return None for non-set attribute if name in LIMITED_NAMES + """ + if name in self.LIMITED_NAMES: + return None + else: + return object.__getattribute__(self, name) + + + def get(self, name): + """ *get* value set before or the default value if name in LIMITIED_NAMES + *get* value in __dict__ if name not in LIMITED_NAMES but in __dict__ + """ + + if name in self.LIMITED_NAMES: + if name in self.__dict__: + value = object.__getattribute__(self, name) + if value is None and name in self.LIMITED_DEFAULTS: + return self.LIMITED_DEFAULTS[name] + else: + return value + else: + if name in self.LIMITED_DEFAULTS: + return self.LIMITED_DEFAULTS[name] + else: + return None + + elif name in self.__dict__: + return self.__dict__[name] + + else: + print "key *%s* is invalid for %s"%(name, self.__class__.__name__) + return None diff --git a/fakeinstall b/fakeinstall new file mode 100644 index 0000000..0c26a79 --- /dev/null +++ b/fakeinstall @@ -0,0 +1,23 @@ +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +# fake install is for setup.py bug (http://bugs.python.org/issue644744) +python setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES +sed -i "s|testkit-lite.1|testkit-lite.1.gz|" INSTALLED_FILES diff --git a/man/testkit-lite.1 b/man/testkit-lite.1 new file mode 100644 index 0000000..3086dcf --- /dev/null +++ b/man/testkit-lite.1 @@ -0,0 +1,142 @@ + Quick Start: + ================= + + At first, prepare one tests.xml file aligned with schema files: /opt/testkit/lite/xsd/testdefinition-syntax.xsd. + + And then, + + 1) You can simply conduct one or more tests.xml: + testkit-lite -f /tests.xml /tests.xml + + 2) You can also arrange several tests.xml to one testsxmlconfig, and run + testkit-lite --testxmlconfig /testsxmlconfig + Also, it supports multi testsxmlconfig: + testkit-lite --testxmlconfig /testsxmlconfig /testsxmlconfig + The testsxmlconfig is like this: + ############################## + # /opt/browser/tests.xml # + # /opt/ofono/tests.xml # + # /opt/gfx/tests.xml # + # /opt/pulseaudio/tests.xml # + ############################## + + 3) If you want to validate one new tests.xml: + testkit-lite -f tests.xml -V + + 4) If you just want to get the statistic (such as the testcases number or the structure), dryrun could help: + testkit-lite -f tests.xml -D + + 5) If you want to execute auto tests: + testkit-lite -f tests.xml + + 6) If you just want to execute manual tests: + testkit-lite -f tests.xml -M + + 7) If you want to execute all the auto and manual tests: + testkit-lite -f tests.xml -A + + 8) If you just want to execute the significant tests: + testkit-lite -f tests.xml -S + + 9) If you want to choose some filters: + testkit-lite -f tests1.xml tests2.xml --level level1 level2 --type type1 type2 --Ntestcase gfx1 gfx2... + Note that the test cases will be filtered out by both black rules and white rules. + + 10) At last, you can freely compose the above paramters together: + testkit-lite -f tests1.xml tests2.xml --testxmlconfig config1 config2 -D -A -S -C --level level1 level2 --type type1 type2 ... + + 11) One more parameter is prepared for customized compatible mode for test report: + testkit-lite -f tests.xml -C + + + Get Results: + ================= + Two kinds of test report will be generated: + 1) .textresult + example: + ===================================TestReport=================================== + TYPE PASS FAIL N/A + ---/usr/share/mnts1.0-distromisc-test/tests.xml XML 0 0 24 + `---Distro Misc SUITE 0 0 24 + |---General shortkeys SET 0 0 2 + | |---general_shortkeys_console_switch CASE 0 0 1 + | `---general_shortkeys_standard_shortkeys CASE 0 0 1 + |---Linux Usability SET 0 0 4 + | |---linux_usability_common_commands CASE 0 0 1 + | |---linux_usability_default_app_and_MIME CASE 0 0 1 + | |---linux_usability_gconf_database CASE 0 0 1 + | `---linux_usability_security_usability CASE 0 0 1 + |---Log system SET 0 0 4 + | |---log_system_UI_process CASE 0 0 1 + | |---log_system_monitor_log_access CASE 0 0 1 + | |---log_system_process_pulseaudio_daemon CASE 0 0 1 + | `---log_system_sreadhead CASE 0 0 1 + |---Non-defult integrated applications SET 0 0 3 + | |---non_default_apps_installation CASE 0 0 1 + | |---non_default_apps_sanity_check CASE 0 0 1 + | `---non_default_apps_uninstallation CASE 0 0 1 + |---Package check SET 0 0 3 + | |---package_check_core_debuginfo CASE 0 0 1 + | |---package_check_core_dependency CASE 0 0 1 + | `---package_check_core_version CASE 0 0 1 + |---Peripheral Devices SET 0 0 6 + | |---peripheral_devices_automount CASE 0 0 1 + | |---peripheral_devices_external_bluetooth CASE 0 0 1 + | |---peripheral_devices_external_cdrom CASE 0 0 1 + | |---peripheral_devices_usb_hotplug CASE 0 0 1 + | |---peripheral_devices_usb_hub CASE 0 0 1 + | `---peripheral_devices_usb_mount_umount CASE 0 0 1 + `---User actions on the stage of system boot-up SET 0 0 2 + |---user_actions_at_bootup_keyboard_operations CASE 0 0 1 + `---user_actions_at_bootup_mouse_operations CASE 0 0 1 + + 2) .xmlresult + xml result files aligned with schema files: /opt/testkit/lite/xsd/testdefinition-results.xsd + example: + + The result will be under /opt/testkit/lite/latest after execution, you can also check the history results in /opt/testkit/lite/yyyy-mm-dd-HH:MM:SS.NNNNNN. + + + Notes: + ================= + testkit-lite's TestLog is stored to /opt/testkit/lite/latest + testkit-lite enables only automatic tests by default + Obviously -A and -M are conflict options. + + + Detail Options: + ================= + Options: + -f, --testxmls Specify the test.xml, support multi test.xml + Refer to --testxmlconfig to add by filelist + --testxmlconfig Specify the file listing group of testxmls + Support multi testxmlconfig files + -D, --dryrun Dry-run the selected test cases + -V, --validate-only Do only input xml validation, do not execute tests + -M, --manual-only Enable only manual tests to be executed + -A, --all Enable both automatic and manual tests + -S, --significant-only + Enable only significant tests to be executed + -C, --compatibleresultxml + use nokia compatible result xml format + --domain Select the specified white-rules filter: domain + --requirement Select the specified white-rules filter: requirement + --testsuite Select the specified white-rules filter: testsuite + --testset Select the specified white-rules filter: testset + --environment Select the specified white-rules filter: environment + --level Select the specified white-rules filter: level + --feature Select the specified white-rules filter: feature + --testcase Select the specified white-rules filter: testcase + --subfeature Select the specified white-rules filter: subfeature + --type Select the specified white-rules filter: type + --Ndomain Select the specified black-rules filter: domain + --Nrequirement Select the specified black-rules filter: requirement + --Ntestsuite Select the specified black-rules filter: testsuite + --Ntestset Select the specified black-rules filter: testset + --Nenvironment Select the specified black-rules filter: environment + --Nlevel Select the specified black-rules filter: level + --Nfeature Select the specified black-rules filter: feature + --Ntestcase Select the specified black-rules filter: testcase + --Nsubfeature Select the specified black-rules filter: subfeature + --Ntype Select the specified black-rules filter: type + -h, --help show this help message and exit diff --git a/manexec.py b/manexec.py new file mode 100644 index 0000000..c269137 --- /dev/null +++ b/manexec.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +import sys +############################################################################### + +def QA(question): + """ questions and answer + """ + sys.stdout.write(question) + sys.stdout.flush() + + return sys.stdin.readline().strip() + + +def manual_exec(step_desc): + """ manual executor, return PASS/FAIL + """ + ans = QA("[ CHECK ] %s\n[ RESULT ] Is it correct?(Y/N)" % step_desc) + + if ans in ['', 'Y', 'y', 'Yes', 'yes', 'YES']: + sys.stdout.write("manual execution PASS\n") + return True + else: + sys.stdout.write("manual execution FAIL\n") + return False diff --git a/postinstall b/postinstall new file mode 100644 index 0000000..4f42434 --- /dev/null +++ b/postinstall @@ -0,0 +1,21 @@ +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +chmod a+wx /opt/testkit/lite diff --git a/runner.py b/runner.py new file mode 100644 index 0000000..d33b7d0 --- /dev/null +++ b/runner.py @@ -0,0 +1,478 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# test engine +# + +import os +import sys +from datetime import datetime +from shutil import copyfile + +from testparser import TestDefinitionParser +from xmlreport import TestResultsXMLReport +from textreport import TestResultsTextReport +from testfilter import TestDefinitionFilter +from validate import validate_xml +from autoexec import shell_exec +from manexec import manual_exec, QA +from unit import * + + +############################################################################### +class TRunner: + + """ + Validate several testdefinition.xml files as input. + Parse the testdefinition.xml files. + Apply filter for each run. + Conduct tests execution. + Report testdefinition-results.xml for each testdefinition.xml. + Report testdefinition-results.text for each testdefinition.xml. + """ + + TEST_SCHEMA_FILE = "/opt/testkit/lite/xsd/testdefinition-syntax.xsd" + RESULT_SCHEMA_FILE = "/opt/testkit/lite/xsd/testdefinition-results.xsd" + + parser = TestDefinitionParser() + xmlreport = TestResultsXMLReport() + textreport = TestResultsTextReport() + + def __init__(self): + + # prepare td filter + self.tdfilter = TestDefinitionFilter() + # selected environment, fill to the TestResults/Set unit before report + self.env = None + + # dryrun + self.bdryrun = False + + # nokia compatible result xml + self.bcompatibleresultxml = False + + # print all cases include those result=="N/A" + self.bfullresultxml = True + + # if validateonly + self.bvalidateonly = False + + # result file + self.resultfile = None + + # reporter + self.reporter_name = None + + def get_reporter(self, engine_name): + from pkg_resources import iter_entry_points + klass = None + + dist_plugins = {} + for ep in iter_entry_points(group="oti.reporter_plugin", name=None): + if not dist_plugins.has_key(ep.dist): + dist_plugins[ep.dist] = {} + dist_plugins[ep.dist][ep.name] = ep.load() + for k, v in dist_plugins.items(): + if v['engine_name'] == engine_name: + klass = v['reporter_klass'] + break + if klass: + print "Sucess to find reporter" + else: + raise NameError("can not find plugin for %s" % engine_name) + return klass + + def set_dryrun(self, bdryrun): + self.bdryrun = bdryrun + + def set_compatible_resultxml(self, bcompatibleresultxml): + self.bcompatibleresultxml = bcompatibleresultxml + + def set_full_resultxml(self, bfullresultxml): + self.bfullresultxml = bfullresultxml + + def set_validateonly(self, bvalidateonly): + self.bvalidateonly = bvalidateonly + + + # /* filter operations fallback to tdfilter + + FILTERS = TestDefinitionFilter.FILTERS + + def add_black_rules(self, **kargs): + """ kargs: key:values - "":["",] + """ + if kargs.get("environment") is not None: + raise ValueError("environment is not allowed in black filter") + self.tdfilter.add_black_rules(**kargs) + + + def add_white_rules(self, **kargs): + """ kargs: key:values - "":["",] + """ + if kargs.get("environment") is not None: + if len(kargs["environment"]) == 1: + self.env = kargs["environment"][0] + else: + raise ValueError("ONE environment value allowed only") + self.tdfilter.add_white_rules(**kargs) + + + def clear_black_rules(self): + self.tdfilter.clear_black_rules() + + + def clear_white_rules(self): + self.tdfilter.clear_white_rules() + + + # filter operations fallback to tdfilter */ + + + def run(self, + testxmlfile, + execdir=None, + resultdir=None): + + """ + testxmlfile: target testxml file + execdir and resultdir: should be the absolute path since TRunner + is the common lib + """ + + # execdir is set to xml directory by default + if execdir is None: + execdir = os.path.dirname(os.path.abspath(testxmlfile)) + # resultdir is set to execdir by default + if resultdir is None: + resultdir = execdir + + # validate the xmlfile + print "[ validate the test xml: %s ]" % testxmlfile + ok = validate_xml(self.TEST_SCHEMA_FILE, testxmlfile) + + if self.bvalidateonly: + return ok + + if ok: + + try: + # parse the xmlfile + print "[ parse the test xml: %s ]" % testxmlfile + td = self.parser.parse(testxmlfile) + ok &= (td is not None) + + # apply filter + print "[ apply filters ]" + ok &= self.tdfilter.apply_filter(td) + + # conduct execution + print "[ testing now ]" + cwd = os.getcwd() + os.chdir(execdir) + if not self.bdryrun: + ok &= self.execute(td, resultdir) + else: + print "Dryrun ..." + os.chdir(cwd) + + # get testresults + tr = TestResults(td) + + # other operations + # 1) fill environment in TestResults unit + tr.environment = self.env + + # prepare report + testresultxmlfile = "%s/%s.xmlresult" % (resultdir, os.path.basename(testxmlfile)) + testresulttextfile = "%s/%s.textresult" % (resultdir, os.path.basename(testxmlfile)) + if not os.path.exists(resultdir): + os.mkdir(resultdir) + + # report the result to xml + print "[ generate the result(XML): %s ]" % testresultxmlfile + if self.bcompatibleresultxml: + self.xmlreport.set_report_mode("testrunner compatible") + else: + self.xmlreport.set_report_mode("TRunner") + self.xmlreport.set_report_nacases(self.bfullresultxml) + outfile = file(testresultxmlfile, "w") + print >> outfile, self.xmlreport.report(tr) + outfile.close() + + # validate the resultfile + print "[ validate result(XML): %s ]" % testresultxmlfile + ok &= validate_xml(self.RESULT_SCHEMA_FILE, testresultxmlfile) + + # report the result using text mode + print "[ generate the result(TEXT): %s ]" % testresulttextfile + outfile = file(testresulttextfile, "w") + print self.textreport.report(tr) + print >> outfile, self.textreport.report(tr) + outfile.close() + + # Make another copy to user specified result file + if self.resultfile: + print "resultfile=%s" % self.resultfile + copyfile(testresultxmlfile, self.resultfile) + + if self.reporter_name: + reporter = self.get_reporter(self.reporter_name)() + reporter.report_test(testresultxmlfile,{}) + + + except Exception, e: + print e + ok &= False + + return ok + + + def execute(self, testdefinition, resultdir=os.getcwd()): + + def fillresult(func): + def ret(c, *args): + ok = func(c, *args) + if None == ok: + c.result = "N/A" + if True == ok: + c.result = "PASS" + if False == ok: + c.result = "FAIL" + return ok + return ret + + + @fillresult + def exec_step(step, timeout, presteps_ok, manual): + """ exec_step will do the actual execution work, handle some + failure, such as timeout/pre_steps_failed, fill the key + data (failureinfo, starttime, endtime, returncode ...) + of step structure except the "result" section + """ + """ presteps_ok: + None: Don't care about presteps_ok + True: presteps are PASS + False: presteps are FAIL + """ + ok = True + + # record starttime + step.start = datetime.today().strftime("%Y-%m-%d %H:%M:%S") + + # judge pre_steps + if False == presteps_ok: + step.return_code,step.stdout,step.stderr = \ + (None, "", "pre_steps failed") + step.failure_info = "pre_steps failed" + ok &= False + else: + # run step + # 1) manual steps + if manual: + ok &= manual_exec(step.command) + # 2) auto steps + else: + step.return_code,step.stdout,step.stderr = \ + shell_exec(step.command, timeout, True) + # judge timeout + if step.return_code is None: + step.failure_info = "timeout" + ok &= False + + """ + presteps/poststeps will ignore "return_code" if "expected_result" + is None, while case steps would always compare "expected_result" + even the expected_result is None (use default value) + """ + # compare with the expected_result when: + # it's case step or + # it's pre/post steps and expected_result is specified + if presteps_ok is not None or \ + (presteps_ok is None and step.expected_result is not None): + if step.return_code != step.get("expected_result"): + ok &= False + + if (presteps_ok is None and step.expected_result is None): + step.stdout +=\ + "expected_result is not specified, so ignore result" + + # record endtime + step.end = datetime.today().strftime("%Y-%m-%d %H:%M:%S") + + return ok + + + def exec_steps(steps, timeout, presteps_ok, manual): + ok = True + for i in xrange(len(steps)): + print " [Step] execute step (%d/%d): %s" %(i+1, len(steps), steps[i].command) + ok &= exec_step(steps[i], timeout, presteps_ok, manual) + # one step fail will lead to steps exit + if False == ok: + return ok + + return ok + + + @fillresult + def exec_case(case, presteps_ok): + # deal with manual tests which has description element + #(XXX it's the requirement of tests.xml exported from testlink, + # all the test steps are in "description" + if case.description and case.composedgenattri.get("manual"): + print ("check the description:\n" + case.description) + # deal with each steps + ok = exec_steps(case.steps, + case.composedgenattri.get("timeout"), + presteps_ok, + case.composedgenattri.get("manual")) + # deal with comment in manual exectuion + if False == ok and case.composedgenattri.get("manual"): + case.comment = QA("Please enter comments:") + + return ok + + + @fillresult + def exec_set(set): + + ok = None + + # fill environment in Set unit + set.environment = self.env + + # execute pre_steps + print " [PreSteps] execute pre steps of set *%s*" \ + % set.composedgenattri.get("name") + presteps_ok =\ + exec_steps(set.presteps, + set.composedgenattri.get("timeout"), + None, + False) # pre_steps are always automated + + # execute cases + for case in set.cases: + if case.runit: + print " [Cases] execute case *%s* " \ + % case.composedgenattri.get("name") + exec_ok = exec_case(case, presteps_ok) + if not case.composedgenattri.get("insignificant"): + if None != ok: + if None != exec_ok: + ok &= exec_ok + else: + ok = exec_ok + if case.getfiles: + for f in case.getfiles: + f = f.strip(' \n\t') + f = f.replace('\n', '') + print " [GetFile] store file %s to %s" % (f, resultdir) + shell_exec("cp %s %s -rf" %(f, resultdir), boutput=True) + + + # deal with get files + print " [GetFiles] get needed files of set *%s*" \ + % set.composedgenattri.get("name") + if set.getfiles: + for f in set.getfiles: + f = f.strip(' \n\t') + f = f.replace('\n', '') + print " [GetFile] store file %s to %s" % (f, resultdir) + shell_exec("cp %s %s -rf" %(f, resultdir), boutput=True) + + # execute post_steps + print " [PostSteps] execute post steps of set *%s*" \ + % set.composedgenattri.get("name") + exec_steps(set.poststeps, + set.composedgenattri.get("timeout"), + None, + False) # post_steps are always automated + + # write each case's log + try: + cursuitelogdir = "%s/%s" %\ + (resultdir, set.psuite.genattri.name) + cursetlogdir = "%s/%s/%s" %\ + (resultdir, set.psuite.genattri.name, set.genattri.name) + if not os.path.exists(cursuitelogdir): + os.mkdir(cursuitelogdir) + if not os.path.exists(cursetlogdir): + os.mkdir(cursetlogdir) + for case in set.cases: + for i in xrange(len(case.steps)): + caselogpath = os.path.join(cursetlogdir, case.genattri.name) + caselogfile = file(caselogpath, "w") + print >> caselogfile, "[stdout]:" + print >> caselogfile, case.steps[i].stdout + print >> caselogfile, "[stderr]:" + print >> caselogfile, case.steps[i].stderr + caselogfile.close() + except Exception, e: + print e + + return ok + + + @fillresult + def exec_suite(suite): + + ok = None + for set in suite.sets: + if set.runit: + print " [Set] execute set *%s*" \ + % set.composedgenattri.get("name") + exec_ok = exec_set(set) + if not set.composedgenattri.get("insignificant"): + if None != ok: + if None != exec_ok: + ok &= exec_ok + else: + ok = exec_ok + return ok + + + @fillresult + def exec_testdefinition(td): + ok = None + for suite in td.suites: + if suite.runit: + print " [Suite] execute suite *%s*" \ + % suite.composedgenattri.get("name") + exec_ok = exec_suite(suite) + if not suite.composedgenattri.get("insignificant"): + if None != ok: + if None != exec_ok: + ok &= exec_ok + else: + ok = exec_ok + return ok + + + # go + try: + if isinstance(testdefinition, TestDefinition): + exec_testdefinition(testdefinition) + return True + except Exception, e: + print e + return False + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4abde03 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,33 @@ +[bdist_rpm] +release = 1 +packager = wei.z.zhang@intel.com +requires = python python-lxml man +install_script = fakeinstall +post_install = postinstall +changelog = * Tue Jul 27 2011 Wang, Jing 1.0.5-1 + - Refresh test-definition schema + - Add support for latest test-definition schema + Tue Jul 14 2011 Wang, Jing 1.0.4-1 + - add -o output option, let user specify result file location + - add external reporter plugin support + Sat Apr 23 2011 Wei, Zhang 1.0.3-1 + - add separate log for each case + Fri Nov 12 2010 Wei, Zhang 1.0.2-1 + - refresh the schema file from http://gitorious.org/qa-tools/test-definition/ + - use explicit step to replace pseudo step for descrption + Wed Sep 27 2010 Wei, Zhang 1.0.1-1 + - add option -T to strip those non-executed cases in result xml + - not print empty presteps or poststeps in result xml + Fri Jul 09 2010 Wei, Zhang 0.6.1-1 + - pre_steps and post_steps are forced automated. (align with nokia's testrunner) + - deal with manual tests which has description element, it's the requirement of tests.xml exported from testlink. + - imporve testparser to handle unicode object from tests.xml + Wed Jul 14 2010 Wei, Zhang 0.6.1-2 + - install directory changes from /opt/testkit-lite to /opt/testkit/lite + - encode to utf8 when xml content is transfered to unicode object by lxml + Mon Jul 19 2010 Wei, Zhang 0.6.1-3 + - add normal user running support for command testkit-lite + - deal with non-params as -h + Wed Jul 21 2010 Wei, Zhang 1.0.0-1 + - for 1.0.0 release + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..14663a5 --- /dev/null +++ b/setup.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# tool setup +# +import os +import glob +from distutils.core import setup + +data_files=[('/opt/testkit/lite/xsd', ['xsd/testdefinition-syntax.xsd', 'xsd/testdefinition-results.xsd']), + ('/opt/testkit/lite/', ['LICENSE']), + ('/opt/testkit/lite/', ['README']), + ('/usr/share/man/man1/', ['man/testkit-lite.1'])] + + +setup(name='testkit-lite', + description='commandline testkit runner', + version ='1.0.5', + long_description='', + author='Wei, Zhang', + author_email='wei.z.zhang@intel.com', + license='GPL', + url='', + download_url='', + scripts=['testkit-lite'], + packages=['testkitlite'], + package_dir={'testkitlite': '.' }, + data_files=data_files, +) diff --git a/str2.py b/str2.py new file mode 100644 index 0000000..4bca475 --- /dev/null +++ b/str2.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# string transfer utils +# + +from types import * + +import sys +STRENCODE = "utf8" + +def str2str(s): + # unify str and unicode to str + + if isinstance(s, unicode): + return s.encode(STRENCODE) + if isinstance(s, str): + return s + + return "" + + +def str2val(s): + + ret = None + try: + ret = eval(str2str(s)) + except: + pass + + return ret + + +def str2bool(s): + + if "TRUE" == str2str(s).upper(): + return True + if "FALSE" == str2str(s).upper(): + return False + + return None + + +def str2number(s): + + val = str2val(str2str(s)) + if type(val) in [IntType, FloatType, LongType]: + return val + else: + return None diff --git a/testfilter.py b/testfilter.py new file mode 100644 index 0000000..24fd45d --- /dev/null +++ b/testfilter.py @@ -0,0 +1,217 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# blackfilter and whilefilter +# + +from unit import * + +class Filter(object): + + FILTER_KEYS = [] + + def __init__(self): + self.black_rules = {} + self.white_rules = {} + + + def __add_rules(self, rules, key, *args): + if key in self.FILTER_KEYS: + rules.setdefault(key, []).extend(args) + else: + print "Not support %s:%s" %(self.__class__.__name__, key) + + + def add_black_rule(self, key, *args): + self.__add_rules(self.black_rules, key, *args) + + + def add_white_rule(self, key, *args): + self.__add_rules(self.white_rules, key, *args) + + + def clear_black_rules(self): + self.black_rules.clear() + + + def clear_white_rules(self): + self.white_rules.clear() + + + def is_ok(self, key, value): + """ apply blackfilter and whitefilter, get the intersect result + value can be list or single value + """ + values = (type(value) == type([]) and [value] or [[value]])[0] + + blacklist = self.black_rules.get(key) + whitelist = self.white_rules.get(key) + return (blacklist is None or 0 == len(set(values) & set(blacklist))) \ + and (whitelist is None or 0 != len(set(values) & set(whitelist))) + + +class UnitFilter(Filter): + """ UnitFilter is abstract to CaseFilter/SetFilter/SuiteFilter only + """ + + COMMON_KEYS = [] + GENATTRI_KEYS = [] + + def __init__(self): + self.FILTER_KEYS = self.COMMON_KEYS + self.GENATTRI_KEYS + Filter.__init__(self) + + def is_ok(self, unit): + """ override FIlter's is_ok to filter one case + """ + try: + values = map(lambda x: unit.get(x), self.COMMON_KEYS) + \ + map(lambda x: unit.composedgenattri.get(x), self.GENATTRI_KEYS) + + def ok(index): + return Filter.is_ok(self, self.FILTER_KEYS[index], values[index]) + + result = map(lambda x: ok(x), xrange(len(self.FILTER_KEYS))) + return False not in result + except Exception, e: + print e + return False + + +class CaseFilter(UnitFilter): + + COMMON_KEYS = ["subfeature"] + GENATTRI_KEYS = ["name", "requirement", "type", "level", "manual", "insignificant"] + + +class SetFilter(UnitFilter): + + COMMON_KEYS = ["feature", "environments"] + GENATTRI_KEYS = ["name"] + + +class SuiteFilter(UnitFilter): + + COMMON_KEYS = ["domain"] + GENATTRI_KEYS = ["name"] + + +class TestDefinitionFilter: + """ Aggregation of CaseFilter/SetFilter/SuiteFilter + """ + + # filter mapping table + FILTERS = { + "testsuite": ["SuiteFilter", "name"], + "testset": ["SetFilter", "name"], + "testcase": ["CaseFilter", "name"], + "domain": ["SuiteFilter", "domain"], + "feature": ["SetFilter", "feature"], + "environment": ["SetFilter", "environments"], # one environment allowed only, so delete 's' + "subfeature": ["CaseFilter", "subfeature"], + "requirement": ["CaseFilter", "requirement"], + "type": ["CaseFilter", "type"], + "level": ["CaseFilter", "level"], + "manual": ["CaseFilter", "manual"], + "insignificant": ["CaseFilter", "insignificant"]} + + + def __init__(self): + + self.casefilter = CaseFilter() + self.setfilter = SetFilter() + self.suitefilter = SuiteFilter() + + + def __dispatch_rules(self, mode, **kargs): + """ mode: blackfilter or whitefilter (True/False) + kargs: key:values - "":["",] + """ + try: + # add rules + for kv in kargs.items(): + flttgt = self.FILTERS.get(kv[0]) + if not flttgt: + raise Exception("not support *%s* filter" %kv[0]) + else: + values = (type(kv[1]) == type([]) and [kv[1]] or [[kv[1]]])[0] + + if mode: + if flttgt[0] == "SuiteFilter": + self.suitefilter.add_black_rule(flttgt[1], *values) + if flttgt[0] == "SetFilter": + self.setfilter.add_black_rule(flttgt[1], *values) + if flttgt[0] == "CaseFilter": + self.casefilter.add_black_rule(flttgt[1], *values) + else: + if flttgt[0] == "SuiteFilter": + self.suitefilter.add_white_rule(flttgt[1], *values) + if flttgt[0] == "SetFilter": + self.setfilter.add_white_rule(flttgt[1], *values) + if flttgt[0] == "CaseFilter": + self.casefilter.add_white_rule(flttgt[1], *values) + + except Exception, e: + print e + return None + + + def add_black_rules(self, **kargs): + """ kargs: key:values - "":["",] + """ + self.__dispatch_rules(True, **kargs) + + + def add_white_rules(self, **kargs): + """ kargs: key:values - "":["",] + """ + self.__dispatch_rules(False, **kargs) + + + def clear_black_rules(self): + self.suitefilter.clear_black_rules() + self.setfilter.clear_black_rules() + self.casefilter.clear_black_rules() + + + def clear_white_rules(self): + self.suitefilter.clear_white_rules() + self.setfilter.clear_white_rules() + self.casefilter.clear_white_rules() + + + def apply_filter(self, testdefinition): + """ filter out one new testdefinition, just modify "runit" + """ + try: + if not isinstance(testdefinition, TestDefinition): + raise TypeError("param should be TestDefinition instance") + + for suite in testdefinition.suites: + suite.runit = self.suitefilter.is_ok(suite) + for set in suite.sets: + set.runit = self.setfilter.is_ok(set) & suite.runit + for case in set.cases: + case.runit = self.casefilter.is_ok(case) & set.runit + return True + except Exception, e: + print e + return False diff --git a/testkit-lite b/testkit-lite new file mode 100644 index 0000000..97d7f95 --- /dev/null +++ b/testkit-lite @@ -0,0 +1,225 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# test runner startup + + +import os +import sys +from optparse import * +from datetime import datetime + +from testkitlite.runner import TRunner +from testkitlite.autoexec import shell_exec + + +LOG_DIR = "/opt/testkit/lite" +COMMON_FILTERS = TRunner.FILTERS.keys() +COMMON_FILTERS.remove('manual') +COMMON_FILTERS.remove('insignificant') + +def varnarg(option, opt_str, value, parser): + + value = [] + + import re + for arg in parser.rargs: + if re.search('^--.+', arg) or \ + re.search('^-[\D]', arg): + break + value.append(arg) + + del parser.rargs[:len(value)] + setattr(parser.values, option.dest, value) + + +try: + + option_list = [ + make_option("-f", "--testxmls", dest="testxmls", + action="callback", callback=varnarg, + help="Specify the test.xml, support multi test.xml \ + Refer to --testxmlconfig to add by filelist"), + make_option("--testxmlconfig", dest="testxmlconfig", + action="callback", callback=varnarg, + help="Specify the file listing group of testxmls \ + Support multi testxmlconfig files"), + + make_option("-D", "--dryrun", dest="bdryrun", + action="store_true", + help="Dry-run the selected test cases"), + make_option("-V", "--validate-only", dest="bvalidateonly", + action="store_true", + help="Do only input xml validation, do not execute tests"), + make_option("-M", "--manual-only", dest="bmanualonly", + action="store_true", + help="Enable only manual tests to be executed"), + make_option("-A", "--auto-and-manual", dest="ball", + action="store_true", + help="Enable both automatic and manual tests"), + make_option("-S", "--significant-only", dest="bsignificantonly", + action="store_true", + help="Enable only significant tests to be executed"), + make_option("-C", "--compatibleresultxml", dest="bcompatibleresultxml", + action="store_true", + help="use nokia compatible result xml format"), + make_option("-T", "--stripresultxml", dest="bstripresultxml", + action="store_true", + help="to strip those non-executed cases from result xml"), + make_option("-o", "--output", dest="resultfile", + help="specify output file for result xml"), + make_option("-r", "--reporter", dest="reportername", + help="specify external reporter for publishing result"), + ] + + option_list.extend(map(lambda flt: \ + make_option("--%s"%flt, dest="w%s"%flt,\ + action="callback", callback=varnarg,\ + help="Select the specified white-rules filter: %s" %flt), COMMON_FILTERS)) + option_list.extend(map(lambda flt: \ + make_option("--N%s"%flt, dest="b%s"%flt,\ + action="callback", callback=varnarg,\ + help="Select the specified black-rules filter: %s" %flt), COMMON_FILTERS)) + + try: + # untrusted behaviour of %%prog + usage = "%%prog [options]\n\ +examples: %%prog -f /tests.xml /tests.xml\n\ + %%prog --testxmlconfig /testsxmlconfig /testsxmlconfig\n\ + %%prog -f tests.xml -V\n\ + %%prog -f tests.xml -D\n\ + %%prog -f tests.xml -A\n\ + %%prog -f tests.xml -M\n\ + %%prog -f tests.xml -S\n\ + %%prog -f tests.xml -C\n\ + %%prog -f tests.xml --level level1 level2 --type type1 type2 ...\n\ + %%prog -f tests.xml -f tests1.xml tests2.xml --testxmlconfig config1 config2 -D -A -S -C --level level1 level2 --type type1 type2 ...\n\ +Note: \n\ + 1) TestLog is stored to %s/latest\n\ + 2) %%prog enables only automatic tests by default"\ + %(LOG_DIR) + except: + usage = None + + # detect non-params + if 1 == len(sys.argv): + sys.argv.append("-h") + + parser = OptionParser(option_list=option_list, usage=usage) + + (options, args) = parser.parse_args() + + # detect conflict + if options.ball and options.bmanualonly: + raise ValueError("-A and -M are conflict") + + # create runner + runner = TRunner() + + # apply dryrun ============================================================ + if options.bdryrun: + runner.set_dryrun(options.bdryrun) + + # apply validateonly ====================================================== + if options.bvalidateonly: + runner.set_validateonly(options.bvalidateonly) + + # apply autoonly ========================================================== + if not options.ball and not options.bmanualonly: + runner.add_white_rules(manual=[False]) + + # apply manualonly ======================================================== + if not options.ball and options.bmanualonly: + runner.add_white_rules(manual=[True]) + + # apply allonly =========================================================== + if options.ball and not options.bmanualonly: + pass + + # apply user specify test result file====================================== + if options.resultfile: + runner.resultfile = options.resultfile + + # apply external reporter plugin====================================== + if options.reportername: + runner.reporter_name = options.reportername + + # apply significantonly =================================================== + if options.bsignificantonly: + runner.add_white_rules(insignificant=[False]) + + # apply result format1 ==================================================== + if options.bcompatibleresultxml: + runner.set_compatible_resultxml(options.bcompatibleresultxml) + + # apply result format2 ==================================================== + if options.bstripresultxml: + runner.set_full_resultxml(False == options.bstripresultxml) + + # apply filter ============================================================ + wfilters = {} + bfilters = {} + for flt in COMMON_FILTERS: + if eval('options.w%s'%flt): + wfilters[flt] = eval('options.w%s'%flt) + if eval('options.b%s'%flt): + bfilters[flt] = eval('options.b%s'%flt) + + runner.add_white_rules(**wfilters) + runner.add_black_rules(**bfilters) + + # collect target testxmls + testxmls = [] + if options.testxmlconfig: + for xmlconfig in options.testxmlconfig: + testxmls += open(xmlconfig, "r").read().strip().split('\n') + if options.testxmls: + testxmls += options.testxmls + + # run multi testxmls ====================================================== + # 1) prepare log dir + session = datetime.today().isoformat('-') + log_dir = os.path.join(LOG_DIR, session) + latest_dir = os.path.join(LOG_DIR, "latest") + ret, out, err = shell_exec("mkdir -p %s && rm -f %s && ln -s %s %s" \ + %(log_dir, latest_dir, log_dir, latest_dir)) + if 0 != ret: + print >> sys.stderr, "Create session log directory failed: %s\n%s " %(out, err) + # 2) run + for testxml in testxmls: + sub_log_dir = os.path.join(log_dir, os.path.abspath(os.path.dirname(testxml)).lstrip('/')) + + ret, out, err = shell_exec("mkdir -p %s" % sub_log_dir) + if 0 != ret: + print >> sys.stderr, "Create %s log directory failed: %s\n%s " %(testxml, out, err) + # FIXME, execdir is temporarily set to testxml pathname + try: + runner.run(testxml, execdir=None, resultdir=sub_log_dir) + except Exception, e: + print e + +except KeyboardInterrupt, e: + print >> sys.stderr, "\n\nExiting on user cancel." + sys.exit(1) + +except Exception, e: + print >> sys.stderr, e + sys.exit(1) diff --git a/testparser.py b/testparser.py new file mode 100644 index 0000000..e48da64 --- /dev/null +++ b/testparser.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# testdefinition-syntax.xsd compatible xml parser +# + +from str2 import * +from unit import * +from lxml import etree + + +############################################################################### +class TestDefinitionParser: + + """testdefinition-syntax.xsd compatible xml parser + """ + + def parse(self, xmlfile): + + try: + tree = etree.parse(xmlfile) + root = tree.getroot() + return self.__parse_testdefinition(root, xmlfile) + except Exception, e: + print e + + + def __parse_generalattributes(self, genattri, element): + + attributes = element.attrib + + genattri.name = str2str(attributes.get("name")) + genattri.description = str2str(attributes.get("description")) + genattri.requirement = str2str(attributes.get("requirement")) + genattri.timeout = str2number(attributes.get("timeout")) + genattri.type = str2str(attributes.get("type")) + genattri.level = str2str(attributes.get("level")) + genattri.manual = str2bool(attributes.get("manual")) + genattri.insignificant = str2bool(attributes.get("insignificant")) + + + def __parse_step(self, stepelement): + + step = Step() + + step.command = str2str(stepelement.text) + step.expected_result = str2number(stepelement.attrib.get("expected_result")) + + return step + + + def __parse_steps(self, stepselement): + + steps = [] + + # deal with each step + for element in stepselement: + if element.tag == "step": + step = self.__parse_step(element) + steps.append(step) + + return steps + + + def __parse_getfiles(self, getelement): + + getfiles = [] + + # deal with each file + for element in getelement: + if element.tag == "file": + getfiles.append(str2str(element.text)) + + return getfiles + + + def __parse_case(self, caseelement): + + case = Case() + + # deal with generalattributes + self.__parse_generalattributes(case.genattri, caseelement) + + # deal with sub-element + for element in caseelement: + if element.tag == "description": + case.description = str2str(element.text) + elif element.tag == "get": + case.getfiles = self.__parse_getfiles(element) + + # deal with step group + case.steps = self.__parse_steps(caseelement) + + # deal with other attributes + attributes = caseelement.attrib + case.subfeature = str2str(attributes.get("subfeature")) + + return case + + + def __parse_set(self, setelement): + + set = Set() + + # deal with generalattributes + self.__parse_generalattributes(set.genattri, setelement) + + # deal with sub-element + for element in setelement: + if element.tag == "case": + case = self.__parse_case(element) + set.addcase(case) + elif element.tag == "description": + set.description = str2str(element.text) + elif element.tag == "pre_steps": + set.presteps = self.__parse_steps(element) + elif element.tag == "post_steps": + set.poststeps = self.__parse_steps(element) + elif element.tag == "get": + set.getfiles = self.__parse_getfiles(element) + elif element.tag == "environments": + for e in element: + if str2str(e.text) == "true": + set.environments.append(e.tag) + + # deal with other attributes + attributes = setelement.attrib + set.feature = str2str(attributes.get("feature")) + + return set + + + def __parse_suite(self, suiteelement): + + suite = Suite() + + # deal with generalattributes + self.__parse_generalattributes(suite.genattri, suiteelement) + + # deal with sub-element + for element in suiteelement: + if element.tag == "set": + set = self.__parse_set(element) + suite.addset(set) + elif element.tag == "description": + suite.description = str2str(element.text) + + # deal with other attributes + attributes = suiteelement.attrib + suite.domain = str2str(attributes.get("domain")) + + return suite + + + def __parse_testdefinition(self, tdelement, xmlfile): + + testdefinition = TestDefinition(xmlfile) + + # deal with sub-element + for element in tdelement: + if element.tag == "suite": + suite = self.__parse_suite(element) + testdefinition.addsuite(suite) + elif element.tag == "description": + testdefinition.description = str2str(element.text) + + # deal with attributes + attributes = tdelement.attrib + testdefinition.version = str2str(attributes.get("version")) + + return testdefinition diff --git a/textreport.py b/textreport.py new file mode 100644 index 0000000..6a66886 --- /dev/null +++ b/textreport.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# text report for TRunner +# + +from str2 import * +from unit import * +from tree import * + +############################################################################### +class TestResultsTextReport: + """text report + """ + + COLUMN = ["TYPE", "PASS", "FAIL", "N/A"] + MIN_IWIDTH = 10 + + def report(self, testresults): + + try: + tr = testresults + + # figure out rwidthfmt and iwidthfmt + # rwidth: width of column for result type + # iwidth: width of column for item(testresult/suite/set/case...) + + iwidth = self.MIN_IWIDTH + iwidth = max(iwidth, Tree.INDENT + len(tr.xmlfile)) + iwidth = max(iwidth, Tree.INDENT*2 + 1 + max([0]+map(lambda x:len(x.composedgenattri.get("name")), tr.suites)) + 1) + for suite in tr.suites: + iwidth = max(iwidth, Tree.INDENT*3 + 2 + max([0]+map(lambda x:len(x.composedgenattri.get("name")), suite.sets)) + 1) + for set in suite.sets: + iwidth = max(iwidth, Tree.INDENT*4 + 3 + max([0]+map(lambda x:len(x.composedgenattri.get("name")), set.cases)) + 1) + + rwidth = (80 - iwidth)/len(self.COLUMN) + rwidth = max(rwidth, max(map(lambda x:len(x), self.COLUMN)) + 1) + width = iwidth + (rwidth)*len(self.COLUMN) + rwidthfmt = "%%%ds" %(rwidth) + iwidthfmt = "%%-%ds" %(iwidth) + iwidthfmt1 = "%%-%ds" %(iwidth - Tree.INDENT) + iwidthfmt2 = "%%-%ds" %(iwidth - Tree.INDENT*2 - 1) + iwidthfmt3 = "%%-%ds" %(iwidth - Tree.INDENT*3 - 2) + iwidthfmt4 = "%%-%ds" %(iwidth - Tree.INDENT*4 - 3) + + tiprow = " "*iwidth + reduce(lambda x,y:eval('"'+rwidthfmt*2+'"%(x,y)'), self.COLUMN) + "\n" + summary = "="*35 + "TestReport" + "="*35 + "\n" + summary += tiprow + + # generate tree + line = eval('"'+iwidthfmt1 + '"% tr.xmlfile') + line += eval('"'+rwidthfmt + '" % "XML"') + for c in self.COLUMN[1:]: + line += eval('"'+rwidthfmt + '"% str(len(tr.case_stat(result=c)))') + tree = Tree(line) + for suite in tr.suites: + line = eval('"'+iwidthfmt2 + '"% suite.composedgenattri.get("name")') + line += eval('"'+rwidthfmt + '" % "SUITE"') + for c in self.COLUMN[1:]: + line += eval('"'+rwidthfmt + '"% str(len(suite.case_stat(result=c)))') + suitenode = tree.addNode(tree.getRoot(), line) + for set in suite.sets: + line = eval('"'+iwidthfmt3 + '"% set.composedgenattri.get("name")') + line += eval('"'+rwidthfmt + '" % "SET"') + for c in self.COLUMN[1:]: + line += eval('"'+rwidthfmt + '"% str(len(set.case_stat(result=c)))') + setnode = tree.addNode(suitenode, line) + for case in set.cases: + line = eval('"'+iwidthfmt4 + '"% case.composedgenattri.get("name")') + line += eval('"'+rwidthfmt + '" % "CASE"') + for c in self.COLUMN[1:]: + line += eval('"'+rwidthfmt + '"% str(len(case.case_stat(result=c)))') + casenode = tree.addNode(setnode, line) + + return summary + str(tree) + + except Exception, e: + print e + return "" diff --git a/tree.py b/tree.py new file mode 100644 index 0000000..224976e --- /dev/null +++ b/tree.py @@ -0,0 +1,450 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# simple tree +# + +import types + +class Node(object): + + @staticmethod + def defaultNodeKey(node): + return node.data + + + @staticmethod + def defaultNodeShow(node): + return node.data + + + def __init__(self, + parent=None, + data=None, + level=-1, + keyfunc=None, + showfunc=None): + self.__parent = parent + self.__data = data + self.__children= [] + self.__level = level + if not None == keyfunc: + self.__keyfunc = keyfunc + else: + self.__keyfunc = self.defaultNodeKey + if not None == showfunc: + self.__showfunc = showfunc + else: + self.__showfunc = self.defaultNodeShow + + # some other suage + self.param1 = None + self.param2 = None + self.param3 = None + self.param4 = None + + + def addChild(self, node): + ''' + Add one child + + @param node: child node + @type node: Node + + @return: None + ''' + childset = set(self.__children) + childset.add(node) + self.__children = list(childset) + self.__children.sort(key=self.__keyfunc) + + + def delChild(self, node): + ''' + Del one child + + @param node: child node + @type node: Node + + @return: None + ''' + childset = set(self.__children) + childset.remove(node) + self.__children = list(childset) + self.__children.sort(key=self.__keyfunc) + + + def setKeyfunc(self, keyfunc): + ''' + set keyfunc for the node + key is for node sort and default findChild in Tree + + @param keyfunc: keyfunc + @type keyfunc: func(Node) + + @return: None + ''' + self.__keyfunc = keyfunc + self.__children.sort(key=self.__keyfunc) + + + def setShowfunc(self, showfunc): + ''' + set showfunc for the node + + @param showfunc: showfunc + @type keyfunc: func(Node) + + @return: None + ''' + self.__showfunc = showfunc + + + @property + def key(self): + return self.__keyfunc(self) + + + @property + def parent(self): + return self.__parent + + + @property + def data(self): + return self.__data + + def setData(self, data): + self.__data = data + + @property + def children(self): + return self.__children + + + @property + def level(self): + return self.__level + + + def __str__(self): + return str(self.__showfunc(self)) + + + +class Tree(object): + + INDENT = 3 + + def __init__(self, + tree_name, + node_keyfunc=None, + node_showfunc=None): + + self.__tree_name = tree_name + self.__nodes_table = dict() # format: {key:[node, node ...], ...} + self.__node_keyfunc = node_keyfunc + self.__node_showfunc = node_showfunc + # the head of the tree use default showfunc(just print data:root) + self.__head = Node(None, tree_name, 0, self.__node_keyfunc) + + + @property + def tree_name(self): + return self.__tree_name + + def getRoot(self): + return self.__head + + def getAllKeys(self): + return self.__nodes_table.keys() + + def addNode(self, parent, node_data): + ''' + Add one node(child) to the specified parent node + + @param parent: parent node handler + @type parent: Node + @param node_data: data for this node(child) + @type node_data: Any + + @return: node(child) handler + @rtype: Node + ''' + + # add to parent's subtree + child = Node(parent=parent, data=node_data, + level=parent.level+1, + keyfunc=self.__node_keyfunc, + showfunc=self.__node_showfunc) + parent.addChild(child) + + # add to tree nodes table + if (self.__nodes_table.has_key(child.key)): + self.__nodes_table[child.key].append (child) + else: + self.__nodes_table[child.key] = [child] + + return child + + + def delNode(self, node): + ''' + Delete one node with child together from the tree + + @param node: node to be deleted + @type node: Node + + @return: deleted node's parent node handler + @rtype: Node + ''' + + # remove from parent's subtree + node.parent.delChild(node) + + def del_from_table(_node): + + # remove from tree nodes table + if (self.__nodes_table.has_key(_node.key)): + self.__nodes_table[_node.key].remove (_node) + if 0 == len(self.__nodes_table[_node.key]): + del self.__nodes_table[_node.key] + + for child in _node.children: + del_from_table(child) + + del_from_table(node) + + + def updateNode(self, node, node_data): + ''' + update one node with new node_data + + @param node: node to be updated + @type node: Node + @param node_data: new data for this node + @type node_data: Any + + @return: None + ''' + old_key = node.key + + node.setData(node_data) + + # remove old from tree nodes table + if (self.__nodes_table.has_key(old_key)): + self.__nodes_table[old_key].remove (node) + if 0 == len(self.__nodes_table[old_key]): + del self.__nodes_table[old_key] + + # add new to tree nodes table + if (self.__nodes_table.has_key(node.key)): + self.__nodes_table[node.key].append (node) + else: + self.__nodes_table[node.key] = [node] + + + def findNode(self, key): + ''' + find node in the tree according to the key + + @param key: key to the node + @type key: same with return of keyfunc + + @return: node + @rtype: Node + ''' + if self.__nodes_table.has_key(key): + return self.__nodes_table[key] + else: + return [] + + + def clear(self): + for child in self.getRoot().children: + self.delNode(child) + + + def setNodeKey(self, keyfunc): + ''' + specify the key func of the node, + key is for node sort and default FindChild + + @param keyfunc: key function + @type keyfunc: func(Node) + + @return: None + ''' + new_nodes_table = dict() + self.__node_keyfunc = keyfunc + + self.__head.setKeyfunc(self.__node_keyfunc) + + for nodes in self.__nodes_table.values(): + for node in nodes: + node.setKeyfunc(self.__node_keyfunc) + + if (new_nodes_table.has_key(node.key)): + new_nodes_table[node.key].append (node) + else: + new_nodes_table[node.key] = [node] + + self.__nodes_table = new_nodes_table + + + def setNodeShow(self, showfunc): + ''' + specify the show func of the node + + @param showfunc: show function + @type showfunc: func(Node) + + @return: None + ''' + self.__node_showfunc = showfunc + + for nodes in self.__nodes_table.values(): + for node in nodes: + node.setShowfunc(self.__node_showfunc) + + + def findNodeBy(self, conditionfunc): + ''' + search by conditionfunc, find out those nodes satifiy: + conditionfunc(node) == true + + @param conditionfunc: condition function + @type conditionfunc: func(Node), return: True/False + + @return: group of nodes + @rtype: list with node + ''' + + # if conditionfunc specified, use conditionfunc + returnNode = [] + for nodes in self.__nodes_table.values(): + for node in nodes: + if conditionfunc(node): + returnNode.append (node) + + return returnNode + + + @staticmethod + def traverse(cb, node, *args): + cb(node, *args) + for child in node.children: + Tree.traverse(cb, child, *args) + + + @staticmethod + def __printNode(node, *args): + ''' + print one node + + @param node: node to be printed + @type node: Node + @param args[0]: holder dict to store holder + @type args[0]: dict with element int:char + @param args[1]: string list to store print info + @type args[1]: list with element: string + @param args[2]: initial level + @type args[2]: int + + @return: None + ''' + if 3 == len(args) and \ + type(args[1]) == types.ListType and \ + type(args[1][0]) == types.StringType and \ + type(args[0]) == types.DictType: + + # make use of param1 to store children count + node.param1 = len(node.children) + + def __update_holder(node): + if node.param1 > 0: + args[0][node.level - args[2]] = '|' + else: + args[0][node.level - args[2]] = ' ' + + # set flag on holder, initially by node itself + __update_holder(node) + + # print tree line + for i in range(node.level - args[2]): + if i == node.level - args[2] - 1 and \ + 1 == node.parent.param1: + args[1][0] += " "*Tree.INDENT + "`" + else: + args[1][0] += " "*Tree.INDENT + args[0][i] + args[1][0] += "-"*Tree.INDENT + str(node) + "\n" + + # decrease traversed child count of parent + if node.parent and node.parent.param1: + node.parent.param1 -= 1 + # set flag on holder, repeatedly update parent + __update_holder(node.parent) + + else: + raise RuntimeError("__printNode: args content is not accepted") + + + @staticmethod + def printNodeTree(node): + ''' + print one node and its children + + @param node: node to be printed + @type node: Node + + @return: print string + @rtype: string + ''' + return_str = [""] + Tree.traverse(Tree.__printNode, node, {}, return_str, node.level) + return return_str[0] + + + @staticmethod + def printNodeTreeReverse(node): + ''' + print one node and its parent + + @param node: node to be printed + @type node: Node + + @return: print string + @rtype: string + ''' + node_branch = [] + p = node + + while p != None: + node_branch.append(p) + p = p.parent + + lines = map(lambda node: node.level * 4 * ' ' + '`-- ' + str(node) + '\n', node_branch) + lines.reverse() + return reduce(lambda x,y: x+y, lines+[""]) + + + def __str__(self): + return Tree.printNodeTree(self.__head) diff --git a/unit.py b/unit.py new file mode 100644 index 0000000..584a5aa --- /dev/null +++ b/unit.py @@ -0,0 +1,361 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# various data unit for testing +# + + +import copy +from types import * + +from basicstruct import LimitedAttributes + +# the testdefinition-syntax.xsd is defined here: +# "http://gitorious.org/qa-tools/test-definition/blobs/master/data/testdefinition-syntax.xsd" + + + +############################################################################### +class GeneralAttributes(LimitedAttributes): + """GeneralAttributes are used in suite/set/case + """ + + LIMITED_NAMES = ["name", "description", "requirement", "timeout", + "type", "level", "manual", "insignificant"] + LIMITED_DEFAULTS = {"name": "", + "description": "", + "requirement": "", + "timeout": 90, + "type": "unknown", + "level": "unknown", + "manual": False, + "insignificant": False} + LIMITED_TYPES = {"name": [StringType], + "description": [StringType], + "requirement": [StringType], + "timeout": [IntType, LongType, FloatType], + "type": [StringType], + "level": [StringType], + "manual": [BooleanType], + "insignificant": [BooleanType]} + LIMITED_VALUES = {} + + + @staticmethod + def compose(child, parent): + """compose one new GeneralAttribute from one child GeneralAttribute + and one parent GeneralAttribute + (child, parent) is like: + (case, set) + (case, suite) + (set, suite) + The compose method could be customized + """ + + new = copy.copy(child) + + for attr in new.__dict__: + + if attr in ["name", "description"]: + continue + + if attr in ["type", "level"] \ + and ("unknown" == new.__dict__[attr] or new.__dict__[attr] is None): + new.__dict__[attr] = parent.__dict__[attr] + continue + + if attr in ["timeout", "manual", "insignificant", "requirement"] \ + and new.__dict__[attr] is None: + new.__dict__[attr] = parent.__dict__[attr] + continue + + return new + + + +############################################################################### +class Step(LimitedAttributes): + """Step + """ + + LIMITED_NAMES = ["expected_result", "result"] + LIMITED_DEFAULTS = {"expected_result": 0, + "result": "N/A"} + LIMITED_TYPES = {"expected_result": [IntType], + "result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self): + + self.command = None + self.return_code = None + self.start = None + self.end = None + self.stdout = None + self.stderr = None + self.failure_info = None + + + +############################################################################### +class Case(LimitedAttributes): + """Case + """ + + LIMITED_NAMES = ["result"] + LIMITED_DEFAULTS = {"result": "N/A"} + LIMITED_TYPES = {"result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self): + + self.genattri = GeneralAttributes() + self.composedgenattri = self.genattri + + self.description = None + self.subfeature = None + self.steps = [] # with list of instances of Step + + self.comment = None + + self.pset = None + + # filter mark + self.runit = True + self.getfiles = [] # with list of file path strings + + + def refreshgenattri(self): + + # update own composedgenattri + self.composedgenattri = \ + GeneralAttributes.compose(self.genattri, self.pset.composedgenattri) + + + def case_stat(self, manual=None, insignificant=None, result=None): + """ return case if it satisfies + """ + if (manual is None or \ + manual == self.composedgenattri.get("manual")) and \ + (insignificant is None or \ + insignificant == self.composedgenattri.get("insignificant")) and \ + (result is None or \ + result == self.get("result")): + return [self] + else: + return [] + + +############################################################################### +class Set(LimitedAttributes): + """Set + """ + + LIMITED_NAMES = ["result"] + LIMITED_DEFAULTS = {"result": "N/A"} + LIMITED_TYPES = {"result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self): + + self.genattri = GeneralAttributes() + self.composedgenattri = self.genattri + + self.description = None + self.presteps = [] # with list of instances of Step + self.poststeps = [] # with list of instances of Step + self.getfiles = [] # with list of file path strings + self.feature = None + self.cases = [] # with list of instances of Case + + # environments is from testdefinition.xml + self.environments = [] # with list of string of environment + # environment is the selected exectuion one in result.testdefinition.xml + self.environment = None + + self.psuite = None + + # filter mark + self.runit = True + + + def addcase(self, case): + + if not isinstance(case, Case): + raise TypeError("param should be Case instance") + + case.pset = self + case.refreshgenattri() + self.cases.append(case) + + + def refreshgenattri(self): + + # update own composedgenattri + self.composedgenattri = \ + GeneralAttributes.compose(self.genattri, self.psuite.genattri) + + # notify each sub case to refresh genattri + for case in self.cases: + case.refreshgenattri() + + + def case_stat(self, manual=None, insignificant=None, result=None): + """ return satisfied case[] + """ +# return filter(lambda x: +# (manual is None or \ +# manual == x.composedgenattri.get("manual")) and \ +# (insignificant is None or \ +# insignificant == x.composedgenattri.get("insignificant")) and \ +# (result is None or \ +# result == x.get("result")), +# self.cases) + + cases = [] + for case in self.cases: + cases.extend(case.case_stat(manual, insignificant, result)) + return cases + + +############################################################################### +class Suite(LimitedAttributes): + """Suite + """ + + LIMITED_NAMES = ["result"] + LIMITED_DEFAULTS = {"result": "N/A"} + LIMITED_TYPES = {"result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self): + + self.genattri = GeneralAttributes() + self.composedgenattri = self.genattri + + self.description = None + self.domain = None + self.sets = [] # with list of instances of Set + + self.ptestdefinition= None + + # filter mark + self.runit = True + + + def addset(self, set): + + if not isinstance(set, Set): + raise TypeError("param should be Set instance") + + set.psuite = self + set.refreshgenattri() + self.sets.append(set) + + + def refreshgenattri(self): + + # notify each sub set to refresh genattri + for set in self.sets: + set.refreshgenattri() + + + def case_stat(self, manual=None, insignificant=None, result=None): + """ return satisfied cases number + """ + cases = [] + for set in self.sets: + cases.extend(set.case_stat(manual, insignificant, result)) + return cases + + +############################################################################### +class TestDefinition(LimitedAttributes): + """TestDefinition + """ + + LIMITED_NAMES = ["result"] + LIMITED_DEFAULTS = {"result": "N/A"} + LIMITED_TYPES = {"result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self, xmlfile=""): + + self.xmlfile = xmlfile + + self.description = None + self.version = None + self.suites = [] # with list of instances of Suite + + + def addsuite(self, suite): + + if not isinstance(suite, Suite): + raise TypeError("param should be Suite instance") + + suite.ptestdefinition = self + suite.refreshgenattri() + self.suites.append(suite) + + + def case_stat(self, manual=None, insignificant=None, result=None): + """ return satisfied cases number + """ + cases = [] + for suite in self.suites: + cases.extend(suite.case_stat(manual, insignificant, result)) + return cases + + + +############################################################################### +class TestResults(LimitedAttributes): + """TestResults + """ + + LIMITED_NAMES = ["result"] + LIMITED_DEFAULTS = {"result": "N/A"} + LIMITED_TYPES = {"result": [StringType]} + LIMITED_VALUES = {"result": ["PASS", "FAIL", "N/A"]} + + def __init__(self, testdefinition): + + if not isinstance(testdefinition, TestDefinition): + raise TypeError("param should be TestDefinition instance") + + self.environment = None + self.hwproduct = None + self.hwbuild = None + + self.xmlfile = testdefinition.xmlfile + self.version = testdefinition.version + self.suites = testdefinition.suites + self.result = testdefinition.result + + + def case_stat(self, manual=None, insignificant=None, result=None): + """ return satisfied cases number + """ + cases = [] + for suite in self.suites: + cases.extend(suite.case_stat(manual, insignificant, result)) + return cases + diff --git a/unittest/autoexec_unittest.py b/unittest/autoexec_unittest.py new file mode 100644 index 0000000..0f721e6 --- /dev/null +++ b/unittest/autoexec_unittest.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unitest for autoexec +# + +import sys +sys.path.append("../") +from autoexec import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestTexec(unittest.TestCase): + + def test01(self): + print "test output -------" + ret = shell_exec("echo 'success' && cat nofile", None, True) + print ret + self.assertEqual(ret, [1, 'success', 'cat: nofile: No such file or directory']) + + def test02(self): + print "test no output -------" + ret = shell_exec("echo 'success' && cat nofile", None, False) + print ret + self.assertEqual(ret, [1, 'success', 'cat: nofile: No such file or directory']) + + def test03(self): + print "test timeout 1-------" + ret = shell_exec("echo 'timeout=2' && sleep 1 && echo 'sleep 1 DONE' && sleep 2 && echo 'sleep 2 DONE'", 2, True) + print ret + self.assertEqual(ret, [None, 'timeout=2\nsleep 1 DONE', '']) + + def test04(self): + print "test timeout 2-------" + ret = shell_exec("echo 'timeout=5' && sleep 1 && echo 'sleep 1 DONE' && sleep 2 && echo 'sleep 2 DONE'", 5, True) + print ret + self.assertEqual(ret, [0, 'timeout=5\nsleep 1 DONE\nsleep 2 DONE', '']) + + +# test +if __name__ == "__main__": + unittest.main() diff --git a/unittest/basicstruct_unittest.py b/unittest/basicstruct_unittest.py new file mode 100644 index 0000000..1f823e3 --- /dev/null +++ b/unittest/basicstruct_unittest.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for basicstruct +# + +import sys +sys.path.append("../") +from basicstruct import * + +############################################################################### +# unittest # +############################################################################### + +import unittest +from types import * + +class TestAttributes(LimitedAttributes): + + LIMITED_NAMES = ["result", "timeout", "freely"] + + LIMITED_DEFAULTS = {"result" : "no", + "timeout": 60} + + LIMITED_TYPES = {"result": [StringType], + "timeout": [IntType, LongType, FloatType]} + + LIMITED_VALUES = {"result": ["yes","no"]} + + +class TestLimitedAttributes(unittest.TestCase): + + def test1(self): + """ normal test + """ + ta = TestAttributes() + ta.result = "yes" + ta.timeout = 80 + + self.assertEqual(ta.result, "yes") + self.assertEqual(ta.get("result"), "yes") + self.assertEqual(ta.timeout, 80) + self.assertEqual(ta.get("timeout"), 80) + + def test2(self): + """ test non-set attribute + """ + ta = TestAttributes() + + self.assertEqual(ta.result, None) + self.assertEqual(ta.get("result"), "no") + self.assertEqual(ta.timeout, None) + self.assertEqual(ta.get("timeout"), 60) + + def test3(self): + """ test TYPE error + """ + ta = TestAttributes() + + ta.result = 0 + + self.assertEqual(ta.result, None) + self.assertEqual(ta.get("result"), "no") + + def test4(self): + """ test VALUE error + """ + ta = TestAttributes() + + ta.result = "unknown" + + self.assertEqual(ta.result, None) + self.assertEqual(ta.get("result"), "no") + + def test5(self): + """ test NAME error + """ + ta = TestAttributes() + + self.assertEqual(ta.get("notinnames"), None) + + ta.notinnames = 0 + + self.assertEqual(ta.notinnames, 0) + self.assertEqual(ta.get("notinnames"), 0) + + + def test6(self): + """ test set to None + """ + ta = TestAttributes() + + ta.timeout = None + + self.assertEqual(ta.timeout, None) + self.assertEqual(ta.get("timeout"), 60) + + + def test7(self): + """ test set value with no value limitation + """ + ta = TestAttributes() + + ta.timeout = 50 + + self.assertEqual(ta.timeout, 50) + self.assertEqual(ta.get("timeout"), 50) + + + def test8(self): + """ test type/value non-limitation attribute + """ + ta = TestAttributes() + + ta.freely = 1 + self.assertEqual(ta.freely, 1) + self.assertEqual(ta.get("freely"), 1) + + ta.freely = "1" + self.assertEqual(ta.freely, "1") + self.assertEqual(ta.get("freely"), "1") + + + def test9(self): + """ test attribute of no-default value + """ + ta = TestAttributes() + self.assertEqual(ta.freely, None) + self.assertEqual(ta.get("freely"), None) + + + def test10(self): + """ test if copiable + """ + import copy + ta1 = TestAttributes() + ta2 = copy.copy(ta1) + +if __name__=="__main__": + unittest.main() diff --git a/unittest/complex.xml b/unittest/complex.xml new file mode 100644 index 0000000..6da5ec2 --- /dev/null +++ b/unittest/complex.xml @@ -0,0 +1,70 @@ + + + A demo XML for debugging parsing XML + + A demo suit + + A demo set, ConnMan + + Check if we can ping local 3G network + ./CM_3G_BigPing.py + + + ./CM_3G_Connect.py + + + ../CM_3G_DeviceIsExist.py + + + Check if 3G device exists + ./CM_3G_HasDHCP.py + + netbook, ivi, handset + + file + + + + A demo set, BT + + Check if we can ping local BT nework + ./CM_BT_BigPing.py + + + ./CM_BT_Connect.py + + netbook, ivi, handset + + file + + + + + A demo suit, Graphics + + A demo set, Fbo + + piglit/bin/fbo-1d + fbo-1d -auto + + + fbo-blit -auto + + netbook, ivi + + file + + + + A demo set, General + + piglit/bin/bgra-sec-color-pointer + bgra-sec-color-pointer -auto + + netbook, ivi + + file + + + + diff --git a/unittest/informal.xml b/unittest/informal.xml new file mode 100644 index 0000000..9ef35e5 --- /dev/null +++ b/unittest/informal.xml @@ -0,0 +1,39 @@ + + + + + + /bin/true + + + /bin/false + + + + /bin/true + + + + /bin/false + + + sleep 3 + sleep 3 + sleep 5 + sleep 5 + + + + + sleep 10 + + + /bin/true + + + + + + + + diff --git a/unittest/manexec b/unittest/manexec new file mode 100644 index 0000000..5fc9c94 --- /dev/null +++ b/unittest/manexec @@ -0,0 +1,31 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +import sys +sys.path.append("../") +from manexec import * + +if __name__=="__main__": + ret = manual_exec("right?") + if ret: + sys.exit(0) + else: + sys.exit(1) diff --git a/unittest/manexec.sh b/unittest/manexec.sh new file mode 100644 index 0000000..7566c28 --- /dev/null +++ b/unittest/manexec.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# + +./manexec < +# +# Description: +# unitest for manexec +# + +import sys +sys.path.append("../") +from autoexec import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestManexec(unittest.TestCase): + + # Y + def test01(self): + ret = shell_exec("./manexec.sh Y")[0] + self.assertEqual(ret, 0) + + # N + def test02(self): + ret = shell_exec("./manexec.sh N")[0] + self.assertNotEqual(ret, 0) + +# test +if __name__ == "__main__": + unittest.main() diff --git a/unittest/manual.xml b/unittest/manual.xml new file mode 100644 index 0000000..dc583d3 --- /dev/null +++ b/unittest/manual.xml @@ -0,0 +1,20 @@ + + + + + + /bin/true + + + + dsfasfdsafsdfdsfdsafasf + + right 1? + right 2? + right 3? + right 4? + + + + + diff --git a/unittest/runauto.py b/unittest/runauto.py new file mode 100644 index 0000000..7dfa8ba --- /dev/null +++ b/unittest/runauto.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# run all unittest +# + +from basicstruct_unittest import * +from runner_unittest import * +from str2_unittest import * +from testfilter_unittest import * +from testparser_unittest import * +from xmlreport_unittest import * +from textreport_unittest import * +from autoexec_unittest import * +from unit_unittest import * +from validate_unittest import * +from tree_unittest import * + +if __name__=="__main__": + unittest.main() diff --git a/unittest/runmanual.py b/unittest/runmanual.py new file mode 100644 index 0000000..4c440af --- /dev/null +++ b/unittest/runmanual.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# run all unittest +# + +import sys +sys.path.append("../") +from runner import * + +import unittest + +class ManualTest(unittest.TestCase): + + def testRunnerManual(self): + + trunner = TRunner() + trunner.add_white_rules(manual=[True]) + ret = trunner.run("manual.xml", os.path.dirname(__file__)) + print "Please check manual.xml.xmlresult manually" + + +if __name__=="__main__": + unittest.main() diff --git a/unittest/runner_unittest.py b/unittest/runner_unittest.py new file mode 100644 index 0000000..d868fde --- /dev/null +++ b/unittest/runner_unittest.py @@ -0,0 +1,91 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for runner +# + +import sys +sys.path.append("../") +from runner import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestTRunner(unittest.TestCase): + + def testRun(self): + + trunner = TRunner() + + ret = trunner.run("informal.xml", os.path.dirname(__file__)) + + self.assertTrue(ret == False) + + ret = trunner.run("simple.xml", os.path.dirname(__file__)) + + self.assertTrue(ret == True) + + def testEnvironmentFilter(self): + + # cannot set more than one values to white rules for environment + trunner = TRunner() + try: + trunner.add_white_rules(environment = ["hardware", "scratchbox"]) + self.assertTrue(False) # cannot reach here + except: + pass + + # black rules for environment is not allowed + try: + trunner.add_black_rules(environment = ["scratchbox"]) + self.assertTrue(False) # cannot reach here + except: + pass + + # normal + trunner.add_white_rules(environment = ["scratchbox"]) + ret = trunner.run("simple.xml", os.path.dirname(__file__)) + self.assertTrue(ret == True) + + + def testGetfiles(self): + + try: + os.remove("/tmp/1.txt") + os.remove("/tmp/2.txt") + os.remove("./1.txt") + os.remove("./2.txt") + except: + pass + trunner = TRunner() + trunner.add_white_rules(testcase=["case1_3"]) + ret = trunner.run("simple.xml", os.path.dirname(__file__)) + self.assertTrue(ret == True) + + self.assertTrue(os.path.exists("1.txt")) + self.assertTrue(os.path.exists("2.txt")) + + +if __name__=="__main__": + unittest.main() diff --git a/unittest/simple.xml b/unittest/simple.xml new file mode 100644 index 0000000..53d3206 --- /dev/null +++ b/unittest/simple.xml @@ -0,0 +1,54 @@ + + + + + + + + false + true + + + + + false + true + + + echo "case1_1" + /bin/true + + + /bin/false + + + + echo "1" > /tmp/1.txt; echo "2" > /tmp/2.txt + /bin/true + + + + /bin/false + + + sleep 1 + sleep 3 + sleep 5 + sleep 5 + + + /tmp/1.txt + /tmp/2.txt + + + + + sleep 10 + + + /bin/true + + + + + diff --git a/unittest/simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml b/unittest/simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml new file mode 100644 index 0000000..a34ae93 --- /dev/null +++ b/unittest/simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml @@ -0,0 +1,55 @@ + + + + + + + + false + true + + + + + false + true + + + echo "case1_1" + /bin/true + + + /bin/false + + + + /bin/true + + + + /bin/false + + + sleep 1 + sleep 3 + sleep 5 + sleep 5 + + + + + sleep 10 + + + /bin/true + + + 1.txt + 2.txt + 3.txt + 4.txt + + + + + diff --git a/unittest/str2_unittest.py b/unittest/str2_unittest.py new file mode 100644 index 0000000..3b0ca02 --- /dev/null +++ b/unittest/str2_unittest.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for str2 +# + +import sys +sys.path.append("../") +from str2 import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestStr2(unittest.TestCase): + + def teststr2val(self): + + self.assertTrue(str2val("#@") == None) + self.assertTrue(str2val("False") == False) + self.assertTrue(str2val("FAlse") == None) + self.assertTrue(str2val("1") == 1) + self.assertTrue(str2val("1L") == 1L) + self.assertTrue(str2val("1.0") == 1.0) + + def teststr2bool(self): + + self.assertTrue(str2bool("True") == True) + self.assertTrue(str2bool("False") == False) + self.assertTrue(str2bool("FAlse") == False) + + def teststr2number(self): + + self.assertTrue(str2number("a1") == None) + self.assertTrue(str2number("1") == 1) + self.assertTrue(str2number("1L") == 1L) + self.assertTrue(str2number("1.0") == 1.0) + +if __name__=="__main__": + unittest.main() diff --git a/unittest/testfilter_unittest.py b/unittest/testfilter_unittest.py new file mode 100644 index 0000000..c45ed0a --- /dev/null +++ b/unittest/testfilter_unittest.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for filter +# + +import sys +sys.path.append("../") +from testfilter import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestFilter(unittest.TestCase): + + class DemoFilter(Filter): + + FILTER_KEYS = ["type", "level"] + + def testAddRules(self): + f = self.DemoFilter() + + f.add_black_rule("shape", 1,2,3) + f.add_black_rule("type", "type1","type2") + f.add_black_rule("type", "type3") + f.add_black_rule("level", "level1") + f.add_black_rule("level", "level2","level3") + f.add_white_rule("type", "type4","type5","type6") + f.add_white_rule("level", "level4","level5","level6") + + self.assertEqual(f.black_rules, + {'type':['type1','type2','type3'], + 'level':['level1','level2','level3']}) + self.assertEqual(f.white_rules, + {'type':['type4','type5','type6'], + 'level':['level4','level5','level6']}) + + print reduce(lambda x,y: "%s=%s\n%s=%s" %(x[0],x[1],y[0],y[1]), + f.black_rules.items()) + print reduce(lambda x,y: "%s=%s\n%s=%s" %(x[0],x[1],y[0],y[1]), + f.white_rules.items()) + + + def testIsOK(self): + + f = self.DemoFilter() + + f.add_white_rule("type", 1,2,3) + self.assertEqual(f.is_ok("type", 2), True) + self.assertEqual(f.is_ok("type", 1), True) + self.assertEqual(f.is_ok("type", 3), True) + + f.add_black_rule("level", 2) + self.assertEqual(f.is_ok("level", 2), False) + self.assertEqual(f.is_ok("level", 3), True) + + f.clear_white_rules() + f.clear_black_rules() + f.add_black_rule("type", 2) + f.add_white_rule("type", 1,2,3) + self.assertEqual(f.is_ok("type", 2), False) + self.assertEqual(f.is_ok("type", 1), True) + self.assertEqual(f.is_ok("type", 3), True) + + + +from testparser import * +class TestCaseFilter(unittest.TestCase): + + def testIsOK(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f1 = CaseFilter() + f2 = CaseFilter() + f1.add_white_rule("insignificant", False) + f1.add_black_rule("type", "functional") + f2.add_black_rule("insignificant", False) + + for suite in td.suites: + for set in suite.sets: + for case in set.cases: + if case.composedgenattri.get("insignificant") == False: + if case.composedgenattri.get("type") == "functional": + self.assertEqual(f1.is_ok(case), False) + else: + self.assertEqual(f1.is_ok(case), True) + self.assertEqual(f2.is_ok(case), False) + else: + self.assertEqual(f1.is_ok(case), False) + self.assertEqual(f2.is_ok(case), True) + + +class TestSetFilter(unittest.TestCase): + + def testIsOK(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f1 = SetFilter() + f2 = SetFilter() + f1.add_white_rule("environments", "hardware") + f1.add_black_rule("feature", "sample feature") + f2.add_white_rule("name", "set1", "set2") + + for suite in td.suites: + for set in suite.sets: + if set.composedgenattri.get("name") == "set0": + self.assertEqual(f1.is_ok(set), True) + self.assertEqual(f2.is_ok(set), False) + if set.composedgenattri.get("name") == "set1": + self.assertEqual(f1.is_ok(set), False) + self.assertEqual(f2.is_ok(set), True) + if set.composedgenattri.get("name") == "set2": + self.assertEqual(f1.is_ok(set), False) + self.assertEqual(f2.is_ok(set), True) + + +class TestSuiteFilter(unittest.TestCase): + + def testIsOK(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f1 = SuiteFilter() + f2 = SuiteFilter() + f1.add_white_rule("domain", "browser") + f2.add_black_rule("name", "trlitereg01_suite0") + f2.add_white_rule("name", "trlitereg01_suite0", "trlitereg01_suite1") + + for suite in td.suites: + if suite.composedgenattri.get("name") == "trlitereg01_suite0": + self.assertEqual(f1.is_ok(suite), True) + self.assertEqual(f2.is_ok(suite), False) + if suite.composedgenattri.get("name") == "trlitereg01_suite1": + self.assertEqual(f1.is_ok(suite), False) + self.assertEqual(f2.is_ok(suite), True) + + +class TestTestDefinitionFilter(unittest.TestCase): + + def num_is_almost_correct(self, td, suitenum, setnum, casenum): + """judge equal or not for testdefinitions""" + _suitenum_, _setnum_, _casenum_ = 0, 0, 0 + _suitenum_ += len(filter(lambda x:x.runit,td.suites)) + for i in xrange(len(td.suites)): + suite = td.suites[i] + _setnum_ += len(filter(lambda x:x.runit,suite.sets)) + for j in xrange(len(suite.sets)): + set = suite.sets[j] + _casenum_ += len(filter(lambda x:x.runit,set.cases)) + + self.assertEqual(_suitenum_, suitenum) + self.assertEqual(_setnum_, setnum) + self.assertEqual(_casenum_, casenum) + + + def testNofilter(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f = TestDefinitionFilter() + + # filter directly + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 6) + + + def testCase1(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f = TestDefinitionFilter() + f.add_white_rules(testcase=["case1_1", "case1_2"]) + + # test filter result + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 2) + + # test clear_white_rules + f.clear_white_rules() + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 6) + + + def testCase2(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f = TestDefinitionFilter() + f.add_black_rules(testset=["set0"], feature=["sample feature"]) + + # test filter result + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 1, 1) + + # test clear_black_rules + f.clear_black_rules() + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 6) + + + def testCase3(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f = TestDefinitionFilter() + f.add_black_rules(testsuite=["trlitereg01_suite1"], domain=["browser"]) + + # test filter result + f.apply_filter(td) + self.num_is_almost_correct(td, 0, 0, 0) + + # test clear_black_rules + f.clear_black_rules() + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 6) + + + def testCase4(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + f = TestDefinitionFilter() + f.add_black_rules(domain=["browser"]) + f.add_white_rules(insignificant=[False]) + f.add_white_rules(type=["functional"]) + + # test filter result + f.apply_filter(td) + self.num_is_almost_correct(td, 1, 3, 1) + + # test clear_black_rules and clear_white_rules + f.clear_black_rules() + f.clear_white_rules() + f.apply_filter(td) + self.num_is_almost_correct(td, 2, 3, 6) + + + +if __name__=="__main__": + unittest.main() diff --git a/unittest/testparser_unittest.py b/unittest/testparser_unittest.py new file mode 100644 index 0000000..5353173 --- /dev/null +++ b/unittest/testparser_unittest.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for testparser +# + +import sys +sys.path.append("../") +from testparser import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestTestDefinitionParser(unittest.TestCase): + + def testParse(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + def testParseNegative(self): + parser = TestDefinitionParser() + td = parser.parse("no_this_file.xml") + self.assertTrue(td == None) + + def testParseEnvironments(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + for suite in td.suites: + for set in suite.sets: + if set.composedgenattri.name == "set1": + self.assertTrue(set.environments==["hardware"]) + return + + # cannot reach here + self.assertTrue(False) + + def testParseGetFiles(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + for suite in td.suites: + for set in suite.sets: + if set.composedgenattri.name == "set1": + self.assertTrue(set.getfiles==["/tmp/1.txt", "/tmp/2.txt"]) + return + + # cannot reach here + self.assertTrue(False) + + +if __name__=="__main__": + unittest.main() diff --git a/unittest/textreport_unittest.py b/unittest/textreport_unittest.py new file mode 100644 index 0000000..653b999 --- /dev/null +++ b/unittest/textreport_unittest.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for testreport +# + +import sys +sys.path.append("../") +from textreport import * +from testparser import * +from runner import TRunner + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestTestResultsTextReport(unittest.TestCase): + + def Report(self, xmlfile): + parser = TestDefinitionParser() + td = parser.parse(xmlfile) + ts = TestResults(td) + self.assertTrue(ts != None) + runner = TRunner() + runner.execute(td) + report = TestResultsTextReport() + + tr = TestResults(td) + + print "default IWIDTH=%d" %report.MIN_IWIDTH + print "\n" + report.report(tr) + + print "set IWIDTH=50" + report.MIN_IWIDTH=50 + print "\n" + report.report(tr) + + print "set IWIDTH=70" + report.MIN_IWIDTH=70 + print "\n" + report.report(tr) + + def testNormal(self): + self.Report("simple.xml") + + def testLongLong(self): + self.Report("simplelonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong.xml") + +if __name__=="__main__": + unittest.main() diff --git a/unittest/tree_test_graph/test1PrintTree.graph b/unittest/tree_test_graph/test1PrintTree.graph new file mode 100644 index 0000000..b1ebd17 --- /dev/null +++ b/unittest/tree_test_graph/test1PrintTree.graph @@ -0,0 +1,43 @@ +---demo + |---1 + | `---2 + | `---3 + | |---4 + | | `---5 + | | `---5 + | | `---5 + | | `---5 + | | `---5 + | `---31 + | |---31 + | | |---1 + | | |---2 + | | |---3 + | | |---4 + | | |---5 + | | |---6 + | | |---7 + | | |---8 + | | `---9 + | `---31 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---6 + |---6 + | `---7 + | `---8 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + `---12 + diff --git a/unittest/tree_test_graph/test2SetNodeKey.graph b/unittest/tree_test_graph/test2SetNodeKey.graph new file mode 100644 index 0000000..4247287 --- /dev/null +++ b/unittest/tree_test_graph/test2SetNodeKey.graph @@ -0,0 +1,43 @@ +---demo + |---12 + |---6 + | `---7 + | `---8 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + `---1 + `---2 + `---3 + |---31 + | |---31 + | | |---9 + | | |---8 + | | |---7 + | | |---6 + | | |---5 + | | |---4 + | | |---3 + | | |---2 + | | `---1 + | `---31 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---6 + `---4 + `---5 + `---5 + `---5 + `---5 + `---5 + diff --git a/unittest/tree_test_graph/test3SetNodeShow.graph b/unittest/tree_test_graph/test3SetNodeShow.graph new file mode 100644 index 0000000..66d3793 --- /dev/null +++ b/unittest/tree_test_graph/test3SetNodeShow.graph @@ -0,0 +1,43 @@ +---demo + |---node is 1 + | `---node is 2 + | `---node is 3 + | |---node is 4 + | | `---node is 5 + | | `---node is 5 + | | `---node is 5 + | | `---node is 5 + | | `---node is 5 + | `---node is 31 + | |---node is 31 + | | |---node is 1 + | | |---node is 2 + | | |---node is 3 + | | |---node is 4 + | | |---node is 5 + | | |---node is 6 + | | |---node is 7 + | | |---node is 8 + | | `---node is 9 + | `---node is 31 + | `---node is 5 + | `---node is 5 + | `---node is 5 + | `---node is 5 + | `---node is 5 + | `---node is 5 + | `---node is 5 + | `---node is 6 + |---node is 6 + | `---node is 7 + | `---node is 8 + | `---node is 9 + | `---node is 9 + | `---node is 9 + | `---node is 9 + | `---node is 9 + | `---node is 9 + | `---node is 9 + | `---node is 9 + `---node is 12 + diff --git a/unittest/tree_test_graph/test4PrintNodeTreeReverse.graph b/unittest/tree_test_graph/test4PrintNodeTreeReverse.graph new file mode 100644 index 0000000..6596a88 --- /dev/null +++ b/unittest/tree_test_graph/test4PrintNodeTreeReverse.graph @@ -0,0 +1,6 @@ +`-- demo + `-- 1 + `-- 2 + `-- 3 + `-- 31 + diff --git a/unittest/tree_test_graph/test5DelNode.graph b/unittest/tree_test_graph/test5DelNode.graph new file mode 100644 index 0000000..b123a22 --- /dev/null +++ b/unittest/tree_test_graph/test5DelNode.graph @@ -0,0 +1,14 @@ +---demo + |---6 + | `---7 + | `---8 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + | `---9 + `---12 + diff --git a/unittest/tree_test_graph/test6UpdateNode.graph b/unittest/tree_test_graph/test6UpdateNode.graph new file mode 100644 index 0000000..1dddada --- /dev/null +++ b/unittest/tree_test_graph/test6UpdateNode.graph @@ -0,0 +1,43 @@ +---demo + |---1 + | `---2 + | `---3 + | |---4 + | | `---5 + | | `---5 + | | `---5 + | | `---5 + | | `---5 + | `---31 + | |---31 + | | |---1 + | | |---2 + | | |---3 + | | |---4 + | | |---5 + | | |---6 + | | |---7 + | | |---8 + | | `---99 + | `---31 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---5 + | `---6 + |---6 + | `---7 + | `---8 + | `---99 + | `---99 + | `---99 + | `---99 + | `---99 + | `---99 + | `---99 + | `---99 + `---12 + diff --git a/unittest/tree_test_graph/test7PrintNodeTree.graph b/unittest/tree_test_graph/test7PrintNodeTree.graph new file mode 100644 index 0000000..ce47b39 --- /dev/null +++ b/unittest/tree_test_graph/test7PrintNodeTree.graph @@ -0,0 +1,21 @@ +---31 + |---31 + | |---1 + | |---2 + | |---3 + | |---4 + | |---5 + | |---6 + | |---7 + | |---8 + | `---9 + `---31 + `---5 + `---5 + `---5 + `---5 + `---5 + `---5 + `---5 + `---6 + diff --git a/unittest/tree_unittest.py b/unittest/tree_unittest.py new file mode 100644 index 0000000..69ecfa5 --- /dev/null +++ b/unittest/tree_unittest.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for tree +# + +import sys +sys.path.append("../") +from tree import * + +############################################################################### +# unittest # +############################################################################### + +import unittest +from autoexec import shell_exec +class TestTree(unittest.TestCase): + + def setUp(self): + l=Tree('demo') + child = l.addNode(l.getRoot(),1) + child = l.addNode(child,2) + child = l.addNode(child,3) + child1 = l.addNode(child,31) + child2 = l.addNode(child1,31) + child3 = l.addNode(child1,31) + child4 = l.addNode(child3,1) + child5 = l.addNode(child3,2) + child6 = l.addNode(child3,3) + child7 = l.addNode(child3,4) + child8 = l.addNode(child3,5) + child9 = l.addNode(child3,6) + child10 = l.addNode(child3,7) + child11 = l.addNode(child3,8) + child12 = l.addNode(child3,9) + child = l.addNode(child,4) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child2,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,5) + child = l.addNode(child,6) + child = l.addNode(l.getRoot(),6) + child = l.addNode(child,7) + child = l.addNode(child,8) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(child,9) + child = l.addNode(l.getRoot(),12) + + self.l = l + self.child1 = child1 + + def test1PrintTree(self): + print "*** original tree ***\n" + print self.l + print >> open("tree_test_graph/test1PrintTree", "w"), self.l + self.assertTrue(shell_exec("diff tree_test_graph/test1PrintTree tree_test_graph/test1PrintTree.graph")[0] == 0) + + + def test2SetNodeKey(self): + print "*** setNodeKey = -node.data ***\n" + def node_key(node): + #return str(node.data) + return -node.data + + self.l.setNodeKey(node_key) + print self.l + print >> open("tree_test_graph/test2SetNodeKey", "w"), self.l + self.assertTrue(shell_exec("diff tree_test_graph/test2SetNodeKey tree_test_graph/test2SetNodeKey.graph")[0] == 0) + + + def test3SetNodeShow(self): + print "*** setNodeShow = \"node is\" + node.data ***\n" + def node_show(node): + return "node is " + str(node.data) + self.l.setNodeShow(node_show) + print self.l + print >> open("tree_test_graph/test3SetNodeShow", "w"), self.l + self.assertTrue(shell_exec("diff tree_test_graph/test3SetNodeShow tree_test_graph/test3SetNodeShow.graph")[0] == 0) + + + def test4PrintNodeTreeReverse(self): + print "*** printNodeTreeReverse ***\n" + print Tree.printNodeTreeReverse(self.child1) + print >> open("tree_test_graph/test4PrintNodeTreeReverse", "w"), Tree.printNodeTreeReverse(self.child1) + self.assertTrue(shell_exec("diff tree_test_graph/test4PrintNodeTreeReverse tree_test_graph/test4PrintNodeTreeReverse.graph")[0] == 0) + + + def test5DelNode(self): + print "*** delNode = (node.data == 1) ***\n" + def conditionfunc(node): + return node.data == 1 + for i in self.l.findNodeBy(conditionfunc): + self.l.delNode(i) + print self.l + print >> open("tree_test_graph/test5DelNode", "w"), self.l + self.assertTrue(shell_exec("diff tree_test_graph/test5DelNode tree_test_graph/test5DelNode.graph")[0] == 0) + + + def test6UpdateNode(self): + print "*** updateNode (data from 9 to 99)***\n" + for i in self.l.findNode(9)[:]: + self.l.updateNode(i, 99) + print self.l + print >> open("tree_test_graph/test6UpdateNode", "w"), self.l + self.assertTrue(shell_exec("diff tree_test_graph/test6UpdateNode tree_test_graph/test6UpdateNode.graph")[0] == 0) + + def test7PrintNodeTree(self): + print "*** printNodeTree ***\n" + print Tree.printNodeTree(self.child1) + print >> open("tree_test_graph/test7PrintNodeTree", "w"), Tree.printNodeTree(self.child1) + self.assertTrue(shell_exec("diff tree_test_graph/test7PrintNodeTree tree_test_graph/test7PrintNodeTree.graph")[0] == 0) + + +if __name__=="__main__": + unittest.main() diff --git a/unittest/unit_unittest.py b/unittest/unit_unittest.py new file mode 100644 index 0000000..79a8f4c --- /dev/null +++ b/unittest/unit_unittest.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for unitunittest for unit +# + +import sys +sys.path.append("../") +from unit import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestGeneralAttributes(unittest.TestCase): + + def setUp(self): + self.ga = GeneralAttributes() + + def tearDown(self): + del self.ga + + def testSetAttribute(self): + + ga = copy.copy(self.ga) + + ga.name = "name" + ga.description = "description" + ga.requirement = "requirement" + ga.timeout = 90.0 + ga.timeout = 90 + ga.timeout = long(90) + ga.type = "type" + ga.level = "level" + ga.manual = True + ga.insignificant = False + + self.assertEqual(ga.get("name"), "name") + self.assertEqual(ga.get("description"), "description") + self.assertEqual(ga.get("requirement"), "requirement") + self.assertEqual(ga.get("timeout"), long(90)) + self.assertEqual(ga.get("type"), "type") + self.assertEqual(ga.get("level"), "level") + self.assertEqual(ga.get("manual"), True) + self.assertEqual(ga.get("insignificant"), False) + + def testGet(self): + + ga = copy.copy(self.ga) + + ga.name = None + + self.assertEqual(ga.get("name"), ga.LIMITED_DEFAULTS["name"]) + self.assertEqual(ga.get("level"), ga.LIMITED_DEFAULTS["level"]) + + + def testSetAttributeNegative(self): + + count = 0 + + ga = copy.copy(self.ga) + + ga.name = 1 + ga.description = 2 + ga.requirement = 3 + ga.type = 4 + ga.level = 5 + ga.timeout = "90" + ga.manual = "false" + ga.insignificant = "false" + + # all attributes are set to None, and *get* will return default value + for attr in ga.LIMITED_NAMES: + self.assertEqual(ga.__dict__[attr], None) + self.assertEqual(ga.get(attr), ga.LIMITED_DEFAULTS[attr]) + + + def testCompose(self): + child = copy.copy(self.ga) + parent = copy.copy(self.ga) + + child.name = "child" + child.description = None + child.requirement = None + child.type = "unknown" + child.level = "child_level" + child.timeout = 100 + child.manual = None + child.insignificant = False + + parent.name = "parent" + parent.description = "parent_desc" + parent.requirement = "parent_require" + parent.type = "parent_type" + parent.level = "parent_level" + parent.timeout = 80 + parent.manual = True + parent.insignificant = True + + ga = GeneralAttributes.compose (child, parent) + + self.assertEqual(ga.name, child.name) + self.assertEqual(ga.description, child.description) + self.assertEqual(ga.requirement, parent.requirement) + self.assertEqual(ga.type, parent.type) + self.assertEqual(ga.level, child.level) + self.assertEqual(ga.timeout, child.timeout) + self.assertEqual(ga.manual, parent.manual) + self.assertEqual(ga.insignificant, child.insignificant) + + +class TestMisc(unittest.TestCase): + + def test1(self): + """ test "result" in step/case/set/suite/testdefinition + """ + + step = Step() + case = Case() + set = Set() + suite = Suite() + td = TestDefinition() + + # default value check + self.assertEqual(step.get("result"), "N/A") + self.assertEqual(case.get("result"), "N/A") + self.assertEqual(set.get("result"), "N/A") + self.assertEqual(suite.get("result"), "N/A") + self.assertEqual(td.get("result"), "N/A") + + # set value check + step.result = "FAIL" + case.result = "FAIL" + set.result = "FAIL" + suite.result = "FAIL" + td.result = "FAIL" + self.assertEqual(step.get("result"), "FAIL") + self.assertEqual(case.get("result"), "FAIL") + self.assertEqual(set.get("result"), "FAIL") + self.assertEqual(suite.get("result"), "FAIL") + self.assertEqual(td.get("result"), "FAIL") + + # illegal value check + step.result = "NOTSUPPORT" + case.result = "NOTSUPPORT" + set.result = "NOTSUPPORT" + suite.result = "NOTSUPPORT" + td.result = "NOTSUPPORT" + self.assertEqual(step.get("result"), "N/A") + self.assertEqual(case.get("result"), "N/A") + self.assertEqual(set.get("result"), "N/A") + self.assertEqual(suite.get("result"), "N/A") + self.assertEqual(td.get("result"), "N/A") + + + +class TestSuiteSetCase(unittest.TestCase): + + def init(self): + + self.case_a1 = Case() + self.case_a2 = Case() + self.case_a3 = Case() + self.case_a4 = Case() + self.case_b1 = Case() + self.case_b2 = Case() + self.case_b3 = Case() + self.case_b4 = Case() + + self.case_a1.genattri.name = "case_a1" + self.case_a2.genattri.type = "unknown" + self.case_a3.genattri.timeout = None + self.case_a4.genattri.requirement = None + self.case_b1.genattri.description = None + self.case_b2.genattri.level = "b2_level" + self.case_b3.genattri.manual = True + self.case_b4.genattri.insignificant = None + + self.set_a = Set() + self.set_b = Set() + + self.set_a.genattri.name = "set_a" + self.set_a.genattri.type = "setAType" + self.set_a.genattri.timeout = 60 + self.set_a.genattri.requirement = None + self.set_b.genattri.description = "set_b_description" + self.set_b.genattri.level = "b_set_level" + self.set_b.genattri.manual = False + self.set_b.genattri.insignificant = None + + self.suite = Suite() + + self.suite.genattri.name = "suite" + self.suite.genattri.type = "suiteType" + self.suite.genattri.timeout = 20 + self.suite.genattri.requirement = "suiteRequirement" + self.suite.genattri.description = "suite_description" + self.suite.genattri.level = "suite_level" + self.suite.genattri.manual = None + self.suite.genattri.insignificant = False + + + def validate(self): + + self.assertTrue(self.case_a1 in self.set_a.cases) + self.assertTrue(self.case_a2 in self.set_a.cases) + self.assertTrue(self.case_a3 in self.set_a.cases) + self.assertTrue(self.case_a4 in self.set_a.cases) + self.assertTrue(self.case_b1 in self.set_b.cases) + self.assertTrue(self.case_b2 in self.set_b.cases) + self.assertTrue(self.case_b3 in self.set_b.cases) + self.assertTrue(self.case_b4 in self.set_b.cases) + self.assertTrue(self.set_a in self.suite.sets) + self.assertTrue(self.set_b in self.suite.sets) + + # test composedgenattri is as expected + self.assertEqual(self.case_a1.composedgenattri.name,"case_a1") + self.assertEqual(self.case_a2.composedgenattri.type, "setAType") + self.assertEqual(self.case_a3.composedgenattri.timeout, 60) + self.assertEqual(self.case_a4.composedgenattri.requirement, "suiteRequirement") + self.assertEqual(self.case_b1.composedgenattri.description, None) + self.assertEqual(self.case_b2.composedgenattri.level, "b2_level") + self.assertEqual(self.case_b3.composedgenattri.manual, True) + self.assertEqual(self.case_b4.composedgenattri.insignificant, False) + + self.assertEqual(self.set_a.composedgenattri.name, "set_a") + self.assertEqual(self.set_a.composedgenattri.type, "setAType") + self.assertEqual(self.set_a.composedgenattri.timeout, 60) + self.assertEqual(self.set_a.composedgenattri.requirement, "suiteRequirement") + self.assertEqual(self.set_b.composedgenattri.description, "set_b_description") + self.assertEqual(self.set_b.composedgenattri.level, "b_set_level") + self.assertEqual(self.set_b.composedgenattri.manual, False) + self.assertEqual(self.set_b.composedgenattri.insignificant, False) + + def setUp(self): + self.init() + + def tearDown(self): + del self.case_a1, self.case_a2, self.case_a3, self.case_a4, \ + self.case_b1, self.case_b2, self.case_b3, self.case_b4 + del self.set_a, self.set_b + del self.suite + + def testAdd1(self): + + self.init() + + # test add turn mode 1 + self.set_a.addcase(self.case_a1) + self.set_a.addcase(self.case_a2) + self.set_a.addcase(self.case_a3) + self.set_a.addcase(self.case_a4) + self.set_b.addcase(self.case_b1) + self.set_b.addcase(self.case_b2) + self.set_b.addcase(self.case_b3) + self.set_b.addcase(self.case_b4) + self.suite.addset(self.set_a) + self.suite.addset(self.set_b) + + self.validate() + + def testAdd2(self): + + self.init() + + # test add turn mode 2 + self.suite.addset(self.set_a) + self.suite.addset(self.set_b) + self.set_a.addcase(self.case_a1) + self.set_a.addcase(self.case_a2) + self.set_a.addcase(self.case_a3) + self.set_a.addcase(self.case_a4) + self.set_b.addcase(self.case_b1) + self.set_b.addcase(self.case_b2) + self.set_b.addcase(self.case_b3) + self.set_b.addcase(self.case_b4) + + self.validate() + + def testAdd3(self): + + self.init() + + # test add turn mode 2 + self.suite.addset(self.set_a) + self.set_a.addcase(self.case_a1) + self.set_a.addcase(self.case_a2) + self.set_a.addcase(self.case_a3) + self.set_a.addcase(self.case_a4) + self.suite.addset(self.set_b) + self.set_b.addcase(self.case_b1) + self.set_b.addcase(self.case_b2) + self.set_b.addcase(self.case_b3) + self.set_b.addcase(self.case_b4) + + self.validate() + + def testStatistic1(self): + from testparser import * + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + self.assertEqual(len(td.case_stat()), 6) + self.assertEqual(len(td.suites[0].case_stat()), 0) + self.assertEqual(len(td.suites[1].case_stat()), 6) + self.assertEqual(len(td.suites[1].sets[0].case_stat()), 0) + self.assertEqual(len(td.suites[1].sets[1].case_stat()), 5) + self.assertEqual(len(td.suites[1].sets[2].case_stat()), 1) + + def testStatistic2(self): + from testparser import * + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + self.assertEqual(len(td.case_stat(manual=True)), 0) + self.assertEqual(len(td.case_stat(manual=False)), 6) + self.assertEqual(len(td.case_stat(insignificant=False)), 4) + self.assertEqual(len(td.case_stat(insignificant=True)), 2) + self.assertEqual(len(td.case_stat(result="N/A")), 6) + self.assertEqual(len(td.case_stat(result="PASS")), 0) + self.assertEqual(len(td.case_stat(manual=False, insignificant=False)), 4) + +if __name__=="__main__": + unittest.main() diff --git a/unittest/validate_unittest.py b/unittest/validate_unittest.py new file mode 100644 index 0000000..c30b440 --- /dev/null +++ b/unittest/validate_unittest.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# validate xml utiles +# + +import sys +sys.path.append("../") +from validate import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestValidator(unittest.TestCase): + + def testValidateFail(self): + ret = validate_xml("../xsd/testdefinition-syntax.xsd", "informal.xml") + self.assertTrue(ret == False) + + def testValidateSchemaNegative(self): + ret = validate_xml("../xsd/testdefinition-syntax1.xsd", "informal.xml") + self.assertTrue(ret == False) + + def testValidateSchema(self): + ret = validate_xml("../xsd/testdefinition-syntax.xsd", "simple.xml") + self.assertTrue(ret == True) + +if __name__=="__main__": + unittest.main() diff --git a/unittest/xmlreport_unittest.py b/unittest/xmlreport_unittest.py new file mode 100644 index 0000000..419519f --- /dev/null +++ b/unittest/xmlreport_unittest.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# unittest for testreport +# + +import sys +sys.path.append("../") +from xmlreport import * +from testparser import * + +############################################################################### +# unittest # +############################################################################### + +import unittest + +class TestTestResultsXMLReport(unittest.TestCase): + + def testReport1(self): + parser = TestDefinitionParser() + td = parser.parse("complex.xml") + self.assertTrue(td != None) + + ts = TestResults(td) + + report = TestResultsXMLReport() + print "\n" + report.report(ts) + + def testReport2(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + + ts = TestResults(td) + + report = TestResultsXMLReport() + print "\n" + report.report(ts) + + def testSetReportMode(self): + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + report = TestResultsXMLReport() + + ts = TestResults(td) + + report.set_report_mode("testrunner compatible") + print "\n" + report.report(ts) + report.set_report_mode("TRunner") + print "\n" + report.report(ts) + + def testValidate(self): + + parser = TestDefinitionParser() + td = parser.parse("simple.xml") + self.assertTrue(td != None) + report = TestResultsXMLReport() + + tr = TestResults(td) + + report.set_report_mode("testrunner compatible") + print >> file("forvalidate-1.xml", "w"), report.report(tr) + + from validate import validate_xml + ret = validate_xml("../xsd/testdefinition-results.xsd", "forvalidate-1.xml") + self.assertTrue(ret == True) + + report.set_report_mode("TRunner") + print >> file("forvalidate-2.xml", "w"), report.report(tr) + + from validate import validate_xml + ret = validate_xml("../xsd/testdefinition-results.xsd", "forvalidate-2.xml") + self.assertTrue(ret == True) + +if __name__=="__main__": + unittest.main() diff --git a/validate.py b/validate.py new file mode 100644 index 0000000..e48fbc7 --- /dev/null +++ b/validate.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# validate xml utils +# + +from lxml import etree + +def validate_xml(schemafile, xmlfile): + try: + schema_doc = etree.parse(schemafile) + schema = etree.XMLSchema(schema_doc) + tree = etree.parse(xmlfile) + if schema and not schema.validate(tree): + raise Exception("schema validation for *%s* error: \n %s" \ + % (xmlfile, schema.error_log)) + return True + except Exception, e: + print "failed to validate xml *%s*:\n%s" %(xmlfile, e) + return False diff --git a/xmlreport.py b/xmlreport.py new file mode 100644 index 0000000..13356e0 --- /dev/null +++ b/xmlreport.py @@ -0,0 +1,350 @@ +#!/usr/bin/python +# +# Copyright (C) 2010, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +# Authors: +# Wei, Zhang +# +# Description: +# testdefinition-results.xsd compatible xml report +# + +from str2 import * +from unit import * +from lxml import etree + + +############################################################################### +class TestResultsXMLReport: + """testdefinition-results.xsd compatible xml report + """ + + """ + nokia compatible mode: + [x]: report attribute "x" only if x is not None + *********************************************************************: + testresults: + version, environment, hwproduct, hwbuild, result(TRunner only) + suite: + name, result(TRunner only) + set: + name, environment, result(TRunner only) + [description] + case: + name, manual, insignificant, result + [description], [requirement], [level], [subfeature], [comment] + step: + command, result + [failure_info] + + TRunner mode: + [x]: report attribute "x" only if x is not None + *********************************************************************: + testresults: + version, environment, hwproduct, hwbuild, result + suite: + name, result + [description], [requirement], [level], [type], [manual], + [timeout], [insignificant] + set: + name, environment, result + [description], [requirement], [level], [type], [manual], + [timeout], [insignificant] + [feature] + case: + name, manual, insignificant, result + [description], [requirement], [level], [type], [timeout] + [subfeature], [comment] + step: + command, result + [failure_info] + """ + + TESTRUNNER_FORMAT = { + "testresults":{ + "RNG":["version", "environment", "hwproduct", "hwbuild"], + "CNG":[], + }, + "suite": { + "RG": ["name"], + "CG": [], + "RNG":[], + "CNG":[], + }, + "set": { + "RG": ["name"], + "CG": ["description"], + "RNG":["environment"], + "CNG":[], + }, + "case": { + "RG": ["name", "manual", "insignificant"], + "CG": ["description", "requirement", "level"], + "RNG":["result"], + "CNG":["subfeature", "comment"], + }, + "step": { + "RNG":["command", "result"], + "CNG":["failure_info"], + }, + } + + TRUNNER_FORMAT = { + "testresults":{ + "RNG":["version", "environment", "hwproduct", "hwbuild"], + "CNG":[], + }, + "suite": { + "RG": ["name"], + "CG": ["description", "requirement", "level", "type", "manual", "timeout", "insignificant"], + "RNG":[], + "CNG":[], + }, + "set": { + "RG": ["name"], + "CG": ["description", "requirement", "level", "type", "manual", "timeout", "insignificant"], + "RNG":["environment"], + "CNG":["feature"], + }, + "case": { + "RG": ["name", "manual", "insignificant"], + "CG": ["description", "requirement", "level", "type", "timeout"], + "RNG":["result"], + "CNG":["subfeature", "comment"], + }, + "step": { + "RNG":["command", "result"], + "CNG":["failure_info"], + }, + } + + def __init__(self): + # attributes_mode: "testrunner compatible / TRunner" + # testrunner compatible: compatible with nokia's testrunner + # TRunner: TRunner report mode, report all available + # attributes, while keep the minium set of + # nokia's testrunner attributes + self.attributes_mode = "testrunner compatible" + self.target_format = self.TESTRUNNER_FORMAT + self.print_nacases = True + + + def set_report_mode(self, mode="testrunner compatible"): + + # select format + if mode == "testrunner compatible": + self.target_format = self.TESTRUNNER_FORMAT + elif mode == "TRunner": + self.target_format = self.TRUNNER_FORMAT + else: return + + self.attributes_mode = mode + + + def set_report_nacases(self, p_nacases=True): + + self.print_nacases = p_nacases + + + def report(self, testresults): + + try: + testresults_root = self.__report_testresults(testresults) + return etree.tostring(testresults_root, + xml_declaration=True, + encoding="UTF-8", + pretty_print=True) + except Exception, e: + print e + return "" + + + def __element_set_attribute(self, element, attri, value): + + if value is not None: + try: + # compatible with xml boolean + if type(value) == BooleanType: + value = str(value).lower() + + element.set(attri, str(value)) + except: + pass + + + def __element_set_text(self, element, value): + + if value is not None: + try: + element.text = str(value) + except: + pass + else: + element.text = "" + + + def __report_required_attributes_group(self, element, unit, required): + for attri in required: + self.__element_set_attribute(element, attri, unit.get(attri) or "") + + + def __report_common_attributes_group(self, element, unit, common): + for attri in common: + self.__element_set_attribute(element, attri, unit.get(attri)) + + + def __report_required_generalattributes(self, element, genattri_container, required): + + composedgenattri = genattri_container.get("composedgenattri") + for attri in required: + self.__element_set_attribute(element, attri, composedgenattri.get(attri)) + + + def __report_common_generalattributes(self, element, genattri_container, common): + + composedgenattri = genattri_container.get("composedgenattri") + for attri in common: + self.__element_set_attribute(element, attri, composedgenattri.__dict__[attri]) + + + def __report_generalattributes(self, element, genattri_container, elementtype): + # Suite/Set/Case only + if elementtype in ["suite", "set", "case"]: + self.__report_required_generalattributes( + element, genattri_container, self.target_format[elementtype]["RG"]) + self.__report_common_generalattributes( + element, genattri_container, self.target_format[elementtype]["CG"]) + + + def __report_commonattributes(self, element, unit, elementtype): + # TestResults/Suite/Set/Case/Step only + if elementtype in ["testresults", "suite", "set", "case", "step"]: + self.__report_required_attributes_group( + element, unit, self.target_format[elementtype]["RNG"]) + self.__report_common_attributes_group( + element, unit, self.target_format[elementtype]["CNG"]) + + + def __report_step(self, step): + + stepelement = etree.Element("step") + + expectedresultelement = etree.Element("expected_result") + returncodeelement = etree.Element("return_code") + startelement = etree.Element("start") + endelement = etree.Element("end") + stdoutelement = etree.Element("stdout") + stderrelement = etree.Element("stderr") + + self.__element_set_text(expectedresultelement, step.get("expected_result")) + self.__element_set_text(returncodeelement, step.get("return_code")) + self.__element_set_text(startelement, step.get("start")) + self.__element_set_text(endelement, step.get("end")) + self.__element_set_text(stdoutelement, step.get("stdout")) + self.__element_set_text(stderrelement, step.get("stderr")) + + stepelement.append(expectedresultelement) + stepelement.append(returncodeelement) + stepelement.append(startelement) + stepelement.append(endelement) + stepelement.append(stdoutelement) + stepelement.append(stderrelement) + + # deal with attributes + self.__report_commonattributes(stepelement, step, "step") + + return stepelement + + + def __report_steps(self, steps, stepselement): + + for step in steps: + stepelement = self.__report_step(step) + stepselement.append(stepelement) + + + def __report_case(self, case): + + caseelement = etree.Element("case") + + # deal with sub-element + self.__report_steps(case.steps, caseelement) + + # deal with attributes + self.__report_generalattributes(caseelement, case, "case") + self.__report_commonattributes(caseelement, case, "case") + + return caseelement + + + def __report_set(self, set): + + setelement = etree.Element("set") + + # add presteps + if len(set.presteps): + prestepelement = etree.Element("pre_steps") + self.__report_steps(set.presteps, prestepelement) + setelement.append(prestepelement) + + # add poststeps + if len(set.poststeps): + poststepelement = etree.Element("post_steps") + self.__report_steps(set.poststeps, poststepelement) + setelement.append(poststepelement) + + # deal with sub-element + for case in set.cases: + if "N/A" != case.get("result") or True == self.print_nacases: + caseelement = self.__report_case(case) + setelement.append(caseelement) + + # deal with attributes + self.__report_generalattributes(setelement, set, "set") + self.__report_commonattributes(setelement, set, "set") + + return setelement + + + def __report_suite(self, suite): + + suiteelement = etree.Element("suite") + + # deal with sub-element + for set in suite.sets: + setelement = self.__report_set(set) + suiteelement.append(setelement) + + # deal with attributes + self.__report_generalattributes(suiteelement, suite, "suite") + self.__report_commonattributes(suiteelement, suite, "suite") + + return suiteelement + + + def __report_testresults(self, testresults): + + tselement = etree.Element("testresults") + + # deal with sub-element + for suite in testresults.suites: + suiteelement = self.__report_suite(suite) + tselement.append(suiteelement) + + # deal with attributes + self.__report_commonattributes(tselement, testresults, "testresults") + + return tselement diff --git a/xsd/testdefinition-results.xsd b/xsd/testdefinition-results.xsd new file mode 100644 index 0000000..0ff95c3 --- /dev/null +++ b/xsd/testdefinition-results.xsd @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsd/testdefinition-syntax.xsd b/xsd/testdefinition-syntax.xsd new file mode 100644 index 0000000..bd13d5b --- /dev/null +++ b/xsd/testdefinition-syntax.xsd @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.7.4