diff options
68 files changed, 4513 insertions, 0 deletions
@@ -0,0 +1 @@ +Donghoon Shin, dhs.shin@samsung.com diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..d25eebd --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,31 @@ +====================== + litmus Release Notes +====================== + +Version 0.1.0 09 Jun 2016 +--------------------------- +- Initial Version + +Version 0.1.1 27 Jun 2016 +--------------------------- +- Add ttyS0 in uarts list +- Release global lock at exception of device.on() function +- Turn off device if keyboard interrupt is raised while flashing or turning on device +- Add timeout for all call/check_output to avoid hang issue + +Version 0.2.0 9 Sep 2016 +--------------------------- +- Remove acmlock routine to improve test performance +- Support flash function for mock device type +- Update test helper functions + +Version 0.2.1 9 Sep 2016 +--------------------------- +- Update import command to use shell-like path expansions +- Update mock device type to avoid sdb error +- Update default templates for u3 and xu3 + +Version 0.3.0 19 Sep 2016 +--------------------------- +- Update projects/topology file location +- Add projects/topology param at command prompt diff --git a/LICENSE.APLv2 b/LICENSE.APLv2 new file mode 100644 index 0000000..5d51900 --- /dev/null +++ b/LICENSE.APLv2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015-2016 Samsung Electronics Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..07fcd5a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,21 @@ +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include AUTHORS +include README.md +include LICENSE.APLv2 +include CHANGES.txt +include MANIFEST.in +include setup.py +recursive-include litmus/templates * diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..06bc30c --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +install: + python3 setup.py install + +clean: + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a58d58b --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +Litmus is an automated testing tool for tizen arm devices. + +Buliding & installing +--------------------- + +1. Change directory name with version postfix and create an orig.tar.gz + + $ mv litmus litmus-0.3.0 + $ tar cvfz litmus-0.3.0.orig.tar.gz litmus-0.3.0 + +1. Build a deb package with debuild + + $ cd litmus-0.3.0 + $ debuild + +2. Install the deb package using dpkg + + $ sudo dpkg -i litmus_0.3.0-1_amd64.deb + + +Getting started +--------------- + +1. Create a litmus project: + + $ litmus mk myproject + +2. Modify <project_path>/userscript.py and <project_path>/conf.yaml + +3. Run the litmus project + + $ litmus run myproject diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..c7a43e7 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,37 @@ +litmus (0.3.0-1) unstable; urgency=low + + * Update projects/topology file location + * Add projects/topology param at command prompt + + -- Donghoon Shin <dhs.shin@samsung.com> Mon, 19 Sep 2016 12:39:00 +0900 + +litmus (0.2.1-1) unstable; urgency=low + + * Update import command to use shell-like path expansions + * Update mock device type to avoid sdb error + * Update default templates for u3 and xu3 + + -- Donghoon Shin <dhs.shin@samsung.com> Fri, 9 Sep 2016 15:00:00 +0900 + +litmus (0.2.0-1) unstable; urgency=low + + * Remove acmlock routine to imporve test performance + * Support flash function for mock device type + * Update test helper functions + + -- Donghoon Shin <dhs.shin@samsung.com> Fri, 9 Sep 2016 10:03:05 +0900 + +litmus (0.1.1-1) unstable; urgency=low + + * Add ttyS0 in uarts list + * Release global lock at exception of device.on() function + * Turn off device if keyboard interrupt is raised while flashing or turning on device + * Add timeout for all call/check_output to avoid hang issue + + -- Donghoon Shin <dhs.shin@samsung.com> Mon, 27 Jun 2016 14:29:31 +0900 + +litmus (0.1.0-1) unstable; urgency=low + + * Initial release + + -- Donghoon Shin <dhs.shin@samsung.com> Thu, 09 Jun 2016 18:08:19 +0900 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..049fa9a --- /dev/null +++ b/debian/control @@ -0,0 +1,27 @@ +Source: litmus +Section: devel +Priority: optional +Maintainer: Donghoon Shin <dhs.shin@samsung.com> +Build-Depends: debhelper (>= 8.0.0), python3 (>= 3.3), python3-setuptools (>= 3.3) +Standards-Version: 3.9.4 +Homepage: http://www.tizen.org +X-Python-Version: >= 3.3 + +Package: litmus +Architecture: any +Depends: ${python3:Depends}, ${misc:Depends}, + python3 (>= 3.3), + python3-serial (>= 2.6), + python3-yaml (>= 3.10), + python3-requests (>= 2.2.1), + python3-bs4 (>= 4.2.1), + python3-fasteners (>= 0.12), + git (>= 1.9), + lthor (>= 2.0), + sdb (>= 2.2.4), + clewarecontrol (>= 4.1), + smartpower (>= 0.1), + heimdall-flash (>= 1.4.1-2), +Description: Lightweight test manager for tizen automated testing + Litmus is a tool for managing test projects and supporting APIs for automated testing. + Supported API : turn on / off device, Run commands / push and pull files over sdb diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..4647046 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,23 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: litmus +Source: http://www.tizen.org + +Files: * +Copyright: 2015-2016 Samsung Electronics Co., Ltd. +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the complete text of the Apache version 2.0 license + can be found in "/usr/share/common-licenses/Apache-2.0". diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..a18c7d9 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +CHANGES.txt +README.md diff --git a/debian/litmus.udev b/debian/litmus.udev new file mode 100644 index 0000000..2630965 --- /dev/null +++ b/debian/litmus.udev @@ -0,0 +1,3 @@ +KERNEL=="hidraw[0-9]*", SUBSYSTEM=="hidraw", MODE="0666" +KERNEL=="ttyUSB[0-9]*", SUBSYSTEM=="tty", MODE="0666" +KERNEL=="ttyS0", SUBSYSTEM="tty", MODE="0666" diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..65629a5 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,41 @@ +#!/bin/sh +# postinst script for litmus +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postinst> `configure' <most-recently-configured-version> +# * <old-postinst> `abort-upgrade' <new version> +# * <conflictor's-postinst> `abort-remove' `in-favour' <package> +# <new-version> +# * <postinst> `abort-remove' +# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' +# <failed-install-package> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + configure) + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +[ -x /sbin/udevadm ] && /sbin/udevadm control --reload-rules + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..dd11900 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,39 @@ +#!/bin/sh +# postrm script for litmus +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +[ -x /sbin/udevadm ] && /sbin/udevadm control --reload-rules + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..e090276 --- /dev/null +++ b/debian/rules @@ -0,0 +1,12 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ --with python3 --buildsystem=pybuild + make clean + +# Commands not to run. +override_dh_strip override_dh_makeshlibs override_dh_shlibdeps override_dh_auto_test: diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..b39d736 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,223 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) + $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Litmus.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Litmus.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Litmus" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Litmus" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..5cc665f --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Litmus documentation build configuration file, created by +# sphinx-quickstart on Thu Apr 7 14:08:57 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +sys.path.insert(0,os.path.abspath('../..')) +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Litmus' +copyright = '2016, Donghoon Shin' +author = 'Donghoon Shin' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. +# "<project> v<release> documentation" by default. +#html_title = 'Litmus v1.0.0' + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +#html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Litmusdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Litmus.tex', 'Litmus Documentation', + 'Donghoon Shin', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'litmus', 'Litmus Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Litmus', 'Litmus Documentation', + author, 'Litmus', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..7f110d2 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,22 @@ +.. Litmus documentation master file, created by + sphinx-quickstart on Thu Apr 7 14:08:57 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Litmus's documentation! +================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/source/litmus.core.rst b/docs/source/litmus.core.rst new file mode 100644 index 0000000..4c82e86 --- /dev/null +++ b/docs/source/litmus.core.rst @@ -0,0 +1,30 @@ +litmus.core package +=================== + +Submodules +---------- + +litmus.core.manager module +-------------------------- + +.. automodule:: litmus.core.manager + :members: + :undoc-members: + :show-inheritance: + +litmus.core.util module +----------------------- + +.. automodule:: litmus.core.util + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: litmus.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/litmus.device.rst b/docs/source/litmus.device.rst new file mode 100644 index 0000000..067b857 --- /dev/null +++ b/docs/source/litmus.device.rst @@ -0,0 +1,21 @@ +litmus.device package +===================== + +Submodules +---------- + +litmus.device.device module +--------------------------- + +.. automodule:: litmus.device.device + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: litmus.device + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/litmus.helper.rst b/docs/source/litmus.helper.rst new file mode 100644 index 0000000..1aa80eb --- /dev/null +++ b/docs/source/litmus.helper.rst @@ -0,0 +1,30 @@ +litmus.helper package +===================== + +Submodules +---------- + +litmus.helper.helper module +--------------------------- + +.. automodule:: litmus.helper.helper + :members: + :undoc-members: + :show-inheritance: + +litmus.helper.tests module +-------------------------- + +.. automodule:: litmus.helper.tests + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: litmus.helper + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/litmus.rst b/docs/source/litmus.rst new file mode 100644 index 0000000..b8ed084 --- /dev/null +++ b/docs/source/litmus.rst @@ -0,0 +1,19 @@ +litmus package +============== + +Subpackages +----------- + +.. toctree:: + + litmus.core + litmus.device + litmus.helper + +Module contents +--------------- + +.. automodule:: litmus + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..2363359 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +litmus +====== + +.. toctree:: + :maxdepth: 4 + + litmus diff --git a/litmus/__init__.py b/litmus/__init__.py new file mode 100644 index 0000000..724e475 --- /dev/null +++ b/litmus/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +__version__ = '0.3.0' +_homedir_ = os.path.expanduser('~') +_confdir_ = os.path.join(_homedir_, '.litmus') +_duts_ = os.path.join(_confdir_, 'topology') +_projects_ = os.path.join(_confdir_, 'projects') +_tmpdir_ = '/tmp' +_path_for_locks_ = '/var/lock/litmus/' +_dev_types_ = ('u3', 'xu3', 'mock', 'empty') diff --git a/litmus/cmds/__init__.py b/litmus/cmds/__init__.py new file mode 100644 index 0000000..4852a96 --- /dev/null +++ b/litmus/cmds/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from configparser import RawConfigParser + + +def load_project_list(projects): + """docstring for load_project_list""" + configparser = RawConfigParser() + configparser.read(projects) + + project_list = [] + for section in configparser.sections(): + item = dict(configparser.items(section)) + item['name'] = section + project_list.append(item) + return project_list diff --git a/litmus/cmds/cmd_adhoc.py b/litmus/cmds/cmd_adhoc.py new file mode 100755 index 0000000..a1de8e6 --- /dev/null +++ b/litmus/cmds/cmd_adhoc.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +from litmus.core.util import call + + +def main(args): + """docstring for main""" + project_path = os.path.abspath(args.project_path) + sys.path.append(project_path) + + call(['chmod', '-R', '775', project_path]) + + import userscript + userscript.main(project_name='adhoc project', + project_path=project_path, + param=args.param, + workingdir=args.workingdir) diff --git a/litmus/cmds/cmd_cp.py b/litmus/cmds/cmd_cp.py new file mode 100755 index 0000000..efbe848 --- /dev/null +++ b/litmus/cmds/cmd_cp.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import logging +from litmus.core.util import copy, call +from litmus.cmds import load_project_list + + +def main(args): + """docstring for main""" + prj_list = load_project_list(args.projects) + + orig = next((prj for prj in prj_list if prj['name'] == args.orig), + None) + if not orig: + raise Exception('Project {0} does not exists'.format(args.orig)) + + new = next((prj for prj in prj_list if prj['name'] == args.new), + None) + if new: + raise Exception('Project {0} already exists'.format(args.new)) + + path = os.path.abspath(os.path.join(os.curdir, args.new)) + if not os.path.exists(path): + logging.debug('copy project {0} to {1}'.format(args.orig, path)) + if not args.description: + description = input('Enter descriptions for this project : ') + else: + description = args.description + os.mkdir(path) + copy(orig['path'], path) + call(['chmod', '-R', '775', path]) + + with open(args.projects, 'a') as f: + f.write('[{0}]\n'.format(args.new)) + f.write('path={0}\n'.format(path)) + f.write('description={0}\n\n'.format(description)) + else: + raise Exception('{0} already exists'.format(path)) diff --git a/litmus/cmds/cmd_dev.py b/litmus/cmds/cmd_dev.py new file mode 100755 index 0000000..fecc4d5 --- /dev/null +++ b/litmus/cmds/cmd_dev.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import logging + + +def load_device_list(topology): + """docstring for load_project_list""" + with open(os.path.abspath(topology), 'r') as f: + logging.debug(f.read()) + + +def main(args): + """docstring for main""" + logging.debug('=====list of all devices in topology=====\n') + load_device_list(args.topology) diff --git a/litmus/cmds/cmd_gt.py b/litmus/cmds/cmd_gt.py new file mode 100755 index 0000000..b89cbe8 --- /dev/null +++ b/litmus/cmds/cmd_gt.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from litmus.helper.gt import main as gt_main + + +def main(args): + """docstring for main""" + gt_main(topology=args.topology) diff --git a/litmus/cmds/cmd_imp.py b/litmus/cmds/cmd_imp.py new file mode 100755 index 0000000..ff59589 --- /dev/null +++ b/litmus/cmds/cmd_imp.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from litmus.cmds import load_project_list +from litmus.core.util import call + + +def main(args): + """docstring for main""" + prj_list = load_project_list(args.projects) + + project = next((prj for prj in prj_list + if prj['name'] == args.project), + None) + if project: + raise Exception('Project {0} already exists'.format(project['name'])) + + if not args.description: + prj_description = input('Enter project descriptions : ') + else: + prj_description = args.description + + if not args.path: + prj_path = input('Enter Project path : ') + else: + prj_path = args.path + + if not prj_path: + raise Exception('Incorrect path!') + + prj_path = os.path.expanduser(prj_path) + + project = next((prj for prj in prj_list + if prj['path'] == prj_path), + None) + + if project: + raise Exception('Project {0} already use this path' + .format(project['name'])) + + if not os.path.exists(os.path.abspath(prj_path)) and\ + not os.path.exists(os.path.abspath(os.path.join(prj_path+'/', + 'userscript.py'))): + raise Exception('There\'s no litmus project scripts at {0}' + .format(prj_path)) + + call(['chmod', '-R', '775', prj_path]) + + with open(args.projects, 'a') as f: + f.write('[{0}]\n'.format(args.project)) + f.write('path={0}\n'.format(os.path.abspath(prj_path))) + f.write('description={0}\n\n'.format(prj_description)) diff --git a/litmus/cmds/cmd_ls.py b/litmus/cmds/cmd_ls.py new file mode 100755 index 0000000..4e96b2c --- /dev/null +++ b/litmus/cmds/cmd_ls.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from litmus.cmds import load_project_list + + +def main(args): + """docstring for main""" + prj_list = load_project_list(args.projects) + logging.debug('=====list of all litmus projects=====') + for loop in prj_list: + logging.debug('{0:10s} ({1} : {2})'.format(loop['name'], + loop['description'], + loop['path'])) diff --git a/litmus/cmds/cmd_mk.py b/litmus/cmds/cmd_mk.py new file mode 100755 index 0000000..40fa12c --- /dev/null +++ b/litmus/cmds/cmd_mk.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import litmus +import logging +from litmus.core.util import copy, call +from litmus import _dev_types_ +from litmus.cmds import load_project_list + + +def main(args): + """docstring for main""" + prj_list = load_project_list(args.projects) + + project = next((prj for prj in prj_list if prj['name'] == args.project), + None) + if project: + raise Exception('Project {0} already exists'.format(args.project)) + + if not args.type: + dev_type = input('Enter the device type ({}): ' + .format('/'.join(_dev_types_))) + else: + dev_type = args.type + + if dev_type not in _dev_types_: + raise Exception('Incorrect device type') + + path = os.path.abspath(os.path.join(os.curdir, args.project)) + if not os.path.exists(path): + if not args.description: + description = input('Enter descriptions for this project : ') + else: + description = args.description + logging.debug('make a new project : {0}'.format(args.project)) + logging.debug('new project path : {0}'.format(path)) + os.mkdir(path) + src = os.path.join(os.path.join(litmus.__path__[0], 'templates'), + dev_type) + copy(src, path) + call(['chmod', '-R', '775', path]) + + with open(args.projects, 'a') as f: + f.write('[{0}]\n'.format(args.project)) + f.write('path={0}\n'.format(path)) + f.write('description={0}\n\n'.format(description)) + else: + raise Exception('{0} already exists'.format(path)) diff --git a/litmus/cmds/cmd_rm.py b/litmus/cmds/cmd_rm.py new file mode 100755 index 0000000..1321f9d --- /dev/null +++ b/litmus/cmds/cmd_rm.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import shutil +import logging +from configparser import RawConfigParser + + +def main(args): + """docstring for main""" + configparser = RawConfigParser() + configparser.read(args.projects) + + if args.project in configparser.sections(): + path = configparser.get(args.project, 'path') + shutil.rmtree(path) + + configparser.remove_section(args.project) + with open(args.projects, 'w') as f: + configparser.write(f) + logging.debug('Project {0} is removed'.format(args.project)) + else: + raise Exception('Project {0} does not exists'.format(args.project)) diff --git a/litmus/cmds/cmd_run.py b/litmus/cmds/cmd_run.py new file mode 100755 index 0000000..6332d6f --- /dev/null +++ b/litmus/cmds/cmd_run.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from litmus.cmds import load_project_list + + +def main(args): + """docstring for main""" + prj_list = load_project_list(args.projects) + project = next((prj for prj in prj_list if prj['name'] == args.project), + None) + if not project: + raise Exception('Project {} does not exists'.format(args.project)) + sys.path.append(project['path']) + + import userscript + userscript.main(project_name=args.project, + project_path=project['path'], + param=args.param, + workingdir=args.workingdir, + topology=args.topology) diff --git a/litmus/core/__init__.py b/litmus/core/__init__.py new file mode 100644 index 0000000..8a4f716 --- /dev/null +++ b/litmus/core/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/litmus/core/exceptions.py b/litmus/core/exceptions.py new file mode 100644 index 0000000..c176ffa --- /dev/null +++ b/litmus/core/exceptions.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class BootError(Exception): + pass diff --git a/litmus/core/manager.py b/litmus/core/manager.py new file mode 100644 index 0000000..332b8bc --- /dev/null +++ b/litmus/core/manager.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import time +import shutil +import hashlib +import logging +import fasteners + +from threading import Lock +from datetime import datetime +from configparser import RawConfigParser +from litmus.device.device import device +from litmus.core.util import copy, init_logger +from litmus import _duts_, _path_for_locks_, _tmpdir_ + + +class _singleton(object): + _shared_state = {} + + def __init__(self): + self.__dict__ = self._shared_state + + +class manager(_singleton): + """ + Litmus manager class. + + This class manages litmus projects and devices. + User can acquire/release devices and + manage working directory from this class. + """ + _comment = '''==================================================== +Init litmus manager : +Lightweight test manager for tizen automated testing +====================================================''' + _all_devices = [] + _duts = [] + _path_for_locks = _path_for_locks_ + _tmpdir = _tmpdir_ + _project_name = None + _project_path = None + _backup_cwd = None + _workingdir = None + _remove_workingdir_at__del__ = False + + def __init__(self, *args, **kwargs): + super(manager, self).__init__() + self.args = args + self.kwargs = kwargs + logging.debug(self._comment) + + if 'topology' in self.kwargs and self.kwargs['topology']: + tp = self.kwargs['topology'] + else: + tp = _duts_ + self._load_configs(tp) + + if 'project_name' in self.kwargs: + self._project_name = self.kwargs['project_name'] + if 'project_path' in self.kwargs: + self._project_path = self.kwargs['project_path'] + else: + self._project_path = os.getcwd() + if 'workingdir' in self.kwargs and self.kwargs['workingdir']: + self._workingdir = os.path.abspath(self.kwargs['workingdir']) + if 'verbose' in self.kwargs and self.kwargs['verbose']: + init_logger() + + def __del__(self): + if self._backup_cwd: + os.chdir(self._backup_cwd) + if self._workingdir and self._remove_workingdir_at__del__: + shutil.rmtree(self._workingdir) + + def acquire_dut(self, devicetype, + max_retry_times=10, retry_delay=10): + """ + Acquire an available device for testing. + + :param str devicetype: device type + :param int max_retry_times: max retry times for device acquisition + :param float retry_delay: delay time for each device acquisition retry + + Example: + >>> mgr = manager() + >>> dut = mgr.acquire_dut('xu3') + + :returns device: acquired device instance + """ + logging.debug('===============Acquire an available DUT===============') + + candidates = [dev for dev in self._all_devices + if dev['dev_type'] == devicetype] + + if candidates: + for times in range(0, max_retry_times): + for dev in candidates: + if not dev['ilock'].acquired: + gotten_tlock = dev['tlock'].acquire(blocking=False) + gotten_ilock = dev['ilock'].acquire(blocking=False) + try: + os.chmod(dev['ilock'].path, 0o664) + except PermissionError: + logging.debug('Can\'t change lock file permission') + + # if acquire tlock and ilock, assign a device. + if gotten_tlock and gotten_ilock: + dut = device.create(manager=self, **dev) + self._duts.append(dut) + logging.debug('{} is assigned.' + .format(dut.get_name())) + return dut + # if acquire tlock only then release it for next time. + elif gotten_tlock and not gotten_ilock: + dev['tlock'].release() + else: + logging.debug('All {}s are busy. Wait {} seconds.' + .format(devicetype, retry_delay)) + time.sleep(retry_delay) + raise Exception('{} device is not available.'.format(devicetype)) + + def release_dut(self, dut=None): + """ + Release acquired devices under test. + + If dut variable is None then all acquired devices will be released. + + :param device dut: device instance + + Example: + >>> mgr.release_dut(dut) + >>> or + >>> mgr.release_dut() + + """ + # TODO: self._duts.remove(dev) doesn't delete device instance. + # release all _duts if dut param is None + if not dut: + for dev in self._duts: + dev.kwargs['tlock'].release() + dev.kwargs['ilock'].release() + dev._release() + self._duts = [] + # if dut param is not None, release the dut + else: + dev = next((d for d in self._duts + if d.get_name() == dut.get_name()), + None) + if dev: + dev.kwargs['tlock'].release() + dev.kwargs['ilock'].release() + dev._release() + self._duts.remove(dev) + + def get_all_acquired_duts(self): + """ + Return a list of all acquired devices + + Example: + >>> mgr.get_all_acquired_duts() + [litmus.device.devicexu3.devicexu3 object at 0x7fb39c94ebe0>] + + :returns list: all acquired devices + """ + return self._duts + + def get_workingdir(self): + """ + Return a working directory of the litmus project. + + Example: + >>> mgr.get_workingdir() + '/home/user/Workspace/test' + + :returns str: working directory + """ + return self._workingdir + + def init_workingdir(self, workingdir=None): + """ + Initialize a working directory. + + If workingdir param is None, manager creates a temporary directory + to use as a workingdir. And manager deletes this temporary directory + when test has finished. + + If workingdir param is not None, manager uses this directory + as a workingdir. + + And then, Manager copies all files under litmus project directory + to working directory. + + :param str workingdir: working directory path + + Example: + >>> mgr.init_workingdir() + >>> mgr.get_workingdir() + '/tmp/82a41636dd39fe6fee4ffb80a7112ee131af8946' + >>> or + >>> mgr.init_workingdir(workingdir='.') + >>> mgr.get_workingdir() + '/home/user/Workspace/test' + """ + if workingdir: + self._workingdir = os.path.abspath(workingdir) + try: + self._backup_cwd = os.getcwd() + if self._workingdir: + os.chdir(self._workingdir) + else: + workingdir_name = str((hashlib.sha1(str(datetime.now()) + .encode()).hexdigest())) + workspace_path = os.path.join(self._tmpdir, workingdir_name) + os.mkdir(workspace_path) + os.chdir(workspace_path) + self._workingdir = workspace_path + self._remove_workingdir_at__del__ = True + logging.debug('working dir: {}'.format(self._workingdir)) + logging.debug('copy all files in project path to workingdir') + copy(self._project_path, os.curdir) + except Exception as e: + logging.debug(e) + raise Exception('Can\'t init workingdir.') + + def _load_configs(self, configpath): + """docstring for _load_configs""" + configparser = RawConfigParser() + configparser.read(configpath) + + for section in configparser.sections(): + items = dict(configparser.items(section)) + items['deviceid'] = section + + # Interproces Lock and Thread Lock + ilock_filename = os.path.join(self._path_for_locks, + items['deviceid']) + items['tlock'] = Lock() + items['ilock'] = fasteners.InterProcessLock(ilock_filename) + + # Append items + self._all_devices.append(items) + + # Add mock device + mock_deviceid = 'MOCK_001' + mock_ilock_filename = os.path.join(self._path_for_locks, mock_deviceid) + mock = {'deviceid': mock_deviceid, + 'dev_type': 'mock', + 'tlock': Lock(), + 'ilock': fasteners.InterProcessLock(mock_ilock_filename)} + self._all_devices.append(mock) diff --git a/litmus/core/util.py b/litmus/core/util.py new file mode 100644 index 0000000..7851bdb --- /dev/null +++ b/litmus/core/util.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +import logging +import yaml +import subprocess +from distutils.dir_util import copy_tree +from distutils.file_util import copy_file + + +def init_logger(): + """ + Add logging.StreamHandler() to print logs on debug screen. + """ + root_logger = logging.getLogger() + console_handler = logging.StreamHandler() + root_logger.addHandler(console_handler) + root_logger.setLevel(logging.DEBUG) + + +def copy(src, dest): + """ + Copy all files under src to dest. + + :param str src: source directory + :param str dest: destination directory + """ + filenames = convert_single_item_to_list(src) + for l in filenames: + if os.path.isdir(l): + copy_tree(src, dest) + else: + copy_file(src, dest) + + +def decode(byte, encoding='ISO-8859-1'): + """ + decode byte string to unicode string. + + :param str byte: byte string + :param str encoding: encoding + """ + unicodestr = byte.decode(encoding) if isinstance(byte, bytes) else byte + return unicodestr + + +def create_instance(clsname, fromstr, *args, **kwargs): + """ + import a module dynamically and create an instance. + + :param str clsname: module name which you want to create an instance + :param str fromstr: location of the module + + :returns module instance: created instance + """ + fromstr = '{}.{}'.format(fromstr, clsname) + __import__(fromstr) + return getattr(sys.modules[fromstr], clsname)(*args, **kwargs) + + +def check_output(cmd, timeout=None, encoding='ISO-8859-1', shell=False, + stderr=None): + """ + Run command with arguments and return its output. + This is a wrapper of subprocess.check_output(). + + Example: + >>> litmus.core.util.check_output(["echo", "Hello World"]) + b'Hello World!\\n' + """ + outs = None + try: + outs = subprocess.check_output(cmd, timeout=timeout, shell=shell, + stderr=stderr) + if outs: + outs = decode(outs, encoding=encoding) + except subprocess.TimeoutExpired as e: + logging.debug('command {} timed out : {}'.format(e.cmd, e.output)) + exc = sys.exc_info() + raise exc[1].with_traceback(exc[2]) + except subprocess.CalledProcessError as e: + logging.debug('command {} return non-zero : {}'.format(e.cmd, + e.output)) + except Exception: + exc = sys.exc_info() + raise exc[1].with_traceback(exc[2]) + + return outs + + +def call(cmd, timeout=None, shell=False, + stdout=None, stderr=None): + """ + Run the command described by args. + Wait for command to complete, then return the returncode attribute. + This is a wrapper of subprocess.call(). + + Example: + >>> litmus.core.util.call(["ls", "-l"]) + 0 + """ + ret = None + try: + ret = subprocess.call(cmd, timeout=timeout, shell=shell, + stdout=stdout, stderr=stderr) + except subprocess.TimeoutExpired: + logging.debug('command {} timed out'.format(cmd)) + exc = sys.exc_info() + raise exc[1].with_traceback(exc[2]) + except Exception: + exc = sys.exc_info() + raise exc[1].with_traceback(exc[2]) + + return ret + + +def convert_single_item_to_list(item): + """ + Convert a item to list and return it. + If item is already list then return the item without change. + """ + return [item] if type(item) is not list else item + + +def find_pattern(pattern, data, groupindex=0): + """ + Find a first match to a regular expression from data buffer. + This also supports groupindex. + + :param str pattern: regular expression + :param str data: data buffer + :param int groupindex: group index + + :returns str: found string from data + """ + if not data: + data = ' ' + + p = re.compile(pattern) + result = p.search(data) + if result: + result = result.group(groupindex) + return result + + +def find_all_pattern(pattern, data): + """ + Find all matches to a regular expression from data buffer. + + :param str pattern: regular expression + :param str data: data buffer + + :returns str: found string from data + """ + if not data: + data = ' ' + p = re.compile(pattern) + result = p.findall(data) + return result + + +def load_yaml(filename): + """ + load a yaml file. + + :param str filename: a yaml filename + + :returns dict: parsed yaml data + """ + with open(filename, 'r') as stream: + data = yaml.load(stream) + return data diff --git a/litmus/device/__init__.py b/litmus/device/__init__.py new file mode 100644 index 0000000..8a4f716 --- /dev/null +++ b/litmus/device/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/litmus/device/cutter.py b/litmus/device/cutter.py new file mode 100644 index 0000000..aa5acd2 --- /dev/null +++ b/litmus/device/cutter.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +from litmus.core.util import create_instance + + +class cutter(object): + """docstring for cutter""" + + __metaclass__ = abc.ABCMeta + _ctype = None + _cport = None + + def __init__(self, *args, **kwargs): + super(cutter, self).__init__() + self._ctype = kwargs['cutter_type'] + self._cport = kwargs['cutter_port'] + + @classmethod + def create(self, *args, **kwargs): + """ + Create a cutter instance. + """ + clsname = 'cutter' + kwargs['cutter_type'] + return create_instance(clsname, + 'litmus.device', + *args, + **kwargs) + + @abc.abstractmethod + def on(self, delay=0): + """ + Turn on the power cutter. + """ + + @abc.abstractmethod + def off(self, delay=0): + """ + Turn off the power cutter. + """ + + @abc.abstractmethod + def is_on(self): + """ + Return whether cutter is turned on or not. + """ diff --git a/litmus/device/cuttercleware4.py b/litmus/device/cuttercleware4.py new file mode 100644 index 0000000..6d73ea3 --- /dev/null +++ b/litmus/device/cuttercleware4.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from litmus.device.cutter import cutter +from litmus.core.util import call, check_output, find_pattern +import time + + +class cuttercleware4(cutter): + """docstring for cuttercleware4""" + + _cindex = None + _cmd = 'clewarecontrol -d {cport} -c 1' + _controlcmd = _cmd + ' -as {cindex} {on_off}' + _getstatuscmd = _cmd + ' -rs {cindex}' + + def __init__(self, *args, **kwargs): + super(cuttercleware4, self).__init__(*args, **kwargs) + self._cindex = kwargs['cleware_index'] + + def on(self, delay=1): + """docstring for on""" + super(cuttercleware4, self).on() + + c = self._controlcmd.format(cport=self._cport, + cindex=self._cindex, + on_off=1) + out = call(c, shell=True, timeout=10) + time.sleep(delay) + return not out + + def off(self, delay=4): + """docstring for off""" + super(cuttercleware4, self).off() + c = self._controlcmd.format(cport=self._cport, + cindex=self._cindex, + on_off=0) + out = call(c, shell=True, timeout=10) + time.sleep(delay) + return not out + + def is_on(self): + """docstring for is_on""" + super(cuttercleware4, self).is_on() + c = self._getstatuscmd.format(cport=self._cport, cindex=self._cindex) + out = check_output(c, shell=True, timeout=10) + + if find_pattern(pattern=r'On', data=out): + return True + else: + return False diff --git a/litmus/device/cuttersmartpower.py b/litmus/device/cuttersmartpower.py new file mode 100644 index 0000000..231733a --- /dev/null +++ b/litmus/device/cuttersmartpower.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import logging +import subprocess +from litmus.device.cutter import cutter +from litmus.core.util import call, check_output, find_pattern + + +class cuttersmartpower(cutter): + """docstring for cuttersmartpower""" + + _cmd = 'smartpower -d {cport}' + _controlcmd = _cmd + ' -p' + _getstatuscmd = _cmd + ' -s 5' + _max_retry_cnt = 50 + + def __init__(self, *args, **kwargs): + super(cuttersmartpower, self).__init__(*args, **kwargs) + + def on(self, delay=1): + """docstring for on""" + super(cuttersmartpower, self).on() + + retry_cnt = 0 + while retry_cnt < self._max_retry_cnt: + + if not self.is_on(): + c = self._controlcmd.format(cport=self._cport) + call(c, shell=True, stderr=subprocess.DEVNULL, timeout=10) + + if self.is_on(): + time.sleep(delay) + return True + else: + logging.debug('Power on failed. Retry') + retry_cnt += 1 + else: + logging.debug('Critical issue on smartpower.') + time.sleep(delay) + return False + + def off(self, delay=4): + """docstring for off""" + super(cuttersmartpower, self).off() + + retry_cnt = 0 + while retry_cnt < self._max_retry_cnt: + + if self.is_on(): + c = self._controlcmd.format(cport=self._cport) + call(c, shell=True, stderr=subprocess.DEVNULL, timeout=10) + + if not self.is_on(): + time.sleep(delay) + return True + else: + logging.debug('Power off failed. Retry') + retry_cnt += 1 + else: + logging.debug('Critical issue on smartpower.') + time.sleep(delay) + return False + + def is_on(self): + """docstring for is_on""" + super(cuttersmartpower, self).is_on() + c = self._getstatuscmd.format(cport=self._cport) + out = check_output(c, shell=True, + stderr=subprocess.DEVNULL, timeout=10) + + if find_pattern(pattern=r'[0-9].[0-9]{3}W', data=out): + return True + else: + return False diff --git a/litmus/device/device.py b/litmus/device/device.py new file mode 100644 index 0000000..9d9123a --- /dev/null +++ b/litmus/device/device.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import time +import serial +import logging +import fasteners +from threading import Thread, Lock +from litmus.core.util import call, check_output +from litmus.core.util import convert_single_item_to_list +from litmus.core.util import find_pattern +from litmus.core.util import decode +from litmus.core.util import create_instance +from litmus.core.util import find_all_pattern +from litmus.core.exceptions import BootError +from litmus.device.cutter import cutter +from litmus import _path_for_locks_ + + +class device(object): + """ + Litmus device class. + User can control device in topology by this class methods. + """ + + _baudrate = 115200 + _readtimeout = 0.5 + _enterkey = b'\r' + _dnmode_cmd = b'thordown' + _username = b'root' + _password = b'tizen' + _vid = '04e8' + _pid = '685d' + _pattern_loginprompt = r'.*login: $' + _pattern_shellprompt = r'.*# .*' + _max_attempt_login_uart_shell = 5 + _max_attempt_attach_sdb = 10 + _retrycnt_at_a_time_sdb = 20 + _max_attempt_boot_retry = 3 + _boot_timeout = 50.0 + _path_for_locks = _path_for_locks_ + + _cutter = None + _uart = None + _manager = None + _global_tlock = Lock() + _global_ilock_path = os.path.join(_path_for_locks_, 'globallock') + _global_ilock = fasteners.InterProcessLock(_global_ilock_path) + + _name = None + _tests = None + + def __init__(self, *args, **kwargs): + super(device, self).__init__() + self.args = args + self.kwargs = kwargs + self._name = kwargs['deviceid'] + + # init a cutter instance. + self._cutter = cutter.create(*args, **kwargs) + # open uart + self._open_uart() + self._manager = kwargs['manager'] + + def __del__(self): + """docstring for __del__""" + self._release() + + def _release(self): + """docstring for _release""" + self._cutter.off(delay=1) + self._close_uart() + + # public methods. + + @classmethod + def create(self, *args, **kwargs): + """ + Create a device instance. + """ + clsname = 'device' + kwargs['dev_type'] + return create_instance(clsname, + 'litmus.device', + *args, + **kwargs) + + def get_name(self): + """ + Return the name of acquired device. + + Example: + >>> dut = mgr.acquire_dut('xu3') + >>> dut.get_name() + 'XU3_001' + + :returns str: device name + """ + return self._name + + def get_id(self): + """ + Return the id of acquired device. + Device instance uses this id for sdb connection. + + Example: + >>> dut = mgr.acquire_dut('xu3') + >>> dut.get_id() + 'XU3_001' + + :returns str: device id + """ + return self.get_name() + + def on(self, powercut_delay=1): + """ + Turn on the device acquired. + + :param float powercut_delay: power-cut delay for cutter + """ + logging.debug('turn on device {}'.format(self.get_name())) + retry_cnt = 0 + while retry_cnt <= self._max_attempt_boot_retry: + try: + self.off(1) + self._cutter.on(powercut_delay) + self._wait_uart_shell_login_prompt() + self._login_uart_shell() + self._set_sdb_deviceid() + self._attach_sdb() + self._sdb_root_on() + return + except KeyboardInterrupt: + self.off(1) + raise Exception('Keyboard interrupt.') + except Exception as e: + logging.debug(e) + retry_cnt += 1 + else: + self.off(1) + raise BootError('Can\'t turn on dut.') + + def off(self, powercut_delay=1): + """ + Trun off the device acquired. + + :param float powercut_delay: power-cut delay for cutter + """ + logging.debug('turn off device {}'.format(self.get_name())) + self._detach_sdb() + self._cutter.off(powercut_delay) + + def is_on(self): + """ + Return whether device is turned on or not. + + Example: + >>> dut.on() + >>> dut.is_on() + True + >>> dut.off() + >>> dut.is_on() + False + + :returns boolean: true if device is turned on, false otherwise. + """ + pattern = '.*{}'.format(self.get_id()) + outs = check_output('sdb devices'.split(), timeout=10) + if find_pattern(pattern, outs): + return True + else: + return False + + def _thor(self, filenames, busid): + """docstring for thor_downloader""" + cmd = 'lthor --busid={0}'.format(busid) + filenames = convert_single_item_to_list(filenames) + for l in filenames: + cmd += ' {}'.format(l) + logging.debug(cmd) + ret = call(cmd.split(), timeout=600) + if ret: + raise Exception('Thor error.') + + def flash(self, filenames, flasher=_thor, waiting=5): + """ + Flash binaries to device. + This function turn on device and turn off device automatically. + + :param dict filenames: filename string or dict + :param func flasher: wrapper function of external flashing tool + :param float waiting: waiting time to acquire cdc_acm device + + Example: + >>> dut.flash(['boot.tar.gz','platform.tar.gz']) + >>> or + >>> dut.flash('platform.tar.gz') + + """ + logging.debug('flash binaries to device : {}'.format(filenames)) + + if not filenames: + raise Exception('There\'s no file to flash.') + try: + self._acquire_global_lock() + time.sleep(waiting) + self._enter_download_mode(self._dnmode_cmd) + time.sleep(waiting) + busid = self._find_usb_busid() + self._release_global_lock() + flasher(self, filenames=filenames, busid=busid) + self._cutter.off() + except (Exception, KeyboardInterrupt) as e: + self._release_global_lock() + logging.debug(e) + raise Exception('Can\'t flash files : {}.'.format(filenames)) + + def run_cmd(self, command, timeout=None): + """ + Run a command on device. + + :param str command: command to run on device + :param float timeout: timeout + + Example: + >>> dut.on() + >>> dut.run_cmd(['ls','-alF','/','|','grep','usr']) + \'drwxr-xr-x 15 root root 4096 Apr 29 2016 usr/\\r\\n\' + + :returns str: stdout of sdb shell command + + """ + c = ['sdb', '-s', self.get_id(), 'shell'] + c.extend(command) + logging.debug(c) + result = check_output(c, timeout=timeout) + return result + + def push_file(self, src, dest, timeout=None): + """ + Push a file from host to destination path of device. + + :param str src: file path from host pc + :param str dest: destination path of device + :param float timeout: timeout + + Example: + >>> dut.push_file('test.png', '/tmp') + + :returns str: stdout of sdb push command + """ + c = ['sdb', '-s', self.get_id(), 'push', src, dest] + result = check_output(c, timeout=timeout) + return result + + def pull_file(self, src, dest, timeout=None): + """ + Pull a file from device to destination path of host. + + :param str src: file path from device + :param str dest: destination path of host pc + :param float timeout: timeout + + Example: + >>> dut.pull_file('/tmp/test.png','.') + + :returns str: stdout of sdb push command + """ + c = ['sdb', '-s', self.get_id(), 'pull', src, dest] + result = check_output(c, timeout=timeout) + return result + + def _read_uart(self, bufsize=100): + """docstring for read_uart""" + readdata = decode(self._uart.read(bufsize)) + logging.debug(readdata) + return readdata + + def _write_uart(self, cmd, returnkey=b'\r'): + """docstring for write_uart""" + self._uart.write(cmd) + time.sleep(0.1) + if returnkey: + self._uart.write(returnkey) + + def add_test(self, func, args): + """ + Add a testcase to device class instance. + + :param func func: function object for test + :param dict args: arguments for test function + + Example: + >>> from litmus.helper.helper import verify_wifi_is_working + >>> dut.add_test(verify_wifi_is_working, + {'wifi_apname': 'setup', + 'wifi_password': '', + 'result_dir': 'result'}) + + """ + if not self._tests: + self._tests = [] + + self._tests.append({'func': func, 'args': args}) + + def del_test(self, func): + """ + Delete a testcase from device class instance. + + :param func func: function object for test + + Example: + >>> from litmus.helper.helper import verify_wifi_is_working + >>> dut.del_test(verify_wifi_is_working) + """ + self._tests = [l for l in self._tests if l['func'] != func] + + def run_tests(self): + """ + Run all testcases. + + Example: + >>> from litmus.helper.helper import verify_wifi_is_working + >>> dut.add_test(verify_wifi_is_working, + {'wifi_apname': 'setup', + 'wifi_password': '', + 'result_dir': 'result'}) + >>> dut.run_tests() + + """ + for l in self._tests: + if isinstance(l['args'], dict): + l['func'](self, **l['args']) + elif isinstance(l['args'], tuple): + l['func'](self, *l['args']) + + # private methods. + + def _flush_uart_buffer(self): + """docstring for flush_uart_buffer""" + self._uart.flushInput() + self._uart.flushOutput() + self._uart.flush() + + def _open_uart(self): + """docstring for open_uart""" + try: + self._uart = serial.Serial(port=self.kwargs['uart_port'], + baudrate=self._baudrate, + timeout=self._readtimeout) + except serial.SerialException as err: + logging.debug(err) + return None + return self._uart + + def _close_uart(self): + """docstring for close_uart""" + if self._uart.isOpen(): + self._uart.close() + + def _find_usb_busid(self): + """docstring for find_usb_busid""" + pattern = 'usb (.*):.*idVendor={0}, idProduct={1}'.format(self._vid, + self._pid) + kernlog = 'cat /var/log/kern.log | grep usb | tail -n 20' + outs = check_output(kernlog, shell=True, timeout=10) + result = find_all_pattern(pattern=pattern, data=outs) + if result: + busid = result[-1] + logging.debug('usb busid : {}'.format(busid)) + else: + raise Exception('Can\'t find usb busid') + + return busid + + def _thread_for_enter_download_mode(self, cmd, count): + """docstring for thread_for_enter_download_mode""" + for loop in range(count*20): + self._uart.write(self._enterkey) + time.sleep(0.05) + self._uart.write(cmd) + for loop in range(2): + time.sleep(0.1) + self._uart.write(self._enterkey) + + def _enter_download_mode(self, cmd, powercut_delay=1, thread_param=10): + """docstring for _enter_download_mode""" + t = Thread(target=self._thread_for_enter_download_mode, + args=(cmd, thread_param, )) + t.start() + self._cutter.off(delay=powercut_delay) + self._cutter.on(delay=powercut_delay) + t.join() + + def _wait_uart_shell_login_prompt(self): + """docstring for _wait_uart_shell_login_prompt""" + logging.debug('===============Print boot logs===============') + + start_time = time.perf_counter() + wait_time = 0 + while wait_time < self._boot_timeout: + if self._uart.inWaiting: + buf = self._read_uart(1000) + if find_pattern(self._pattern_loginprompt, data=buf): + logging.debug('Found login shell pattern from uart log') + logging.debug('wait_time : {}'.format(wait_time)) + return + elif len(buf) == 0: + self._write_uart(b'') + time.sleep(0.01) + wait_time = time.perf_counter() - start_time + else: + raise Exception('Boot timeout : {}s'.format(wait_time)) + + def _login_uart_shell(self): + """docstring for _login_uart_shell""" + logging.debug('===============Login UART shell===============') + retrycnt = 0 + while retrycnt < self._max_attempt_login_uart_shell: + if self._username: + self._write_uart(self._username) + time.sleep(0.5) + if self._password: + self._write_uart(self._password) + time.sleep(1.5) + self._flush_uart_buffer() + self._write_uart(b'dmesg -n 1') + time.sleep(0.5) + readdata = self._read_uart(2000) + if find_pattern(self._pattern_shellprompt, readdata): + return + else: + logging.debug('Login failed. retry.') + self._write_uart(b'') + time.sleep(2) + retrycnt += 1 + else: + raise Exception('Can\'t login uart shell.') + + def _set_sdb_deviceid(self): + """docstring for _set_sdb_deviceid""" + usb0_path = b'/sys/class/usb_mode/usb0' + pattern = '.*{0}'.format(self.get_id()) + + def set_serialnumber(deviceid): + """docstring for set_serialnumber""" + self._write_uart(b''.join([b'echo 0 > ', usb0_path, b'/enable'])) + time.sleep(0.3) + self._write_uart(b''.join([b'echo ', + b'-n ', + deviceid, + b' > ', usb0_path, + b'/iSerial'])) + time.sleep(0.3) + self._write_uart(b''.join([b'echo 1 > ', usb0_path, b'/enable'])) + time.sleep(0.3) + + def check_funcs_sconf(): + """docstring for check_funcs_sconf""" + self._write_uart(b''.join([b'cat ', usb0_path, b'/funcs_sconf'])) + time.sleep(0.3) + self._write_uart(b''.join([b'cat ', usb0_path, b'/enable'])) + time.sleep(0.3) + self._read_uart(bufsize=1000) + + def get_serialnumber(): + """docstring for get_serialnumber""" + self._write_uart(b''.join([b'cat ', usb0_path, b'/iSerial'])) + time.sleep(0.3) + return self._read_uart(1000) + + retrycnt = 0 + while retrycnt < 10: + set_serialnumber(deviceid=self.get_id().encode()) + check_funcs_sconf() + serialnumber = get_serialnumber() + if find_pattern(pattern, serialnumber): + return + retrycnt += 1 + else: + raise Exception('Can\'t configure sdb deviceid') + + def _attach_sdb(self): + """docstring for _attach_sdb""" + # start sdb server if it is not started. + call('sdb start-server'.split(), timeout=10) + + retry_attempt = 0 + pattern = r'{}.*device.*\t.*'.format(self.get_id()) + + while retry_attempt < self._max_attempt_attach_sdb: + for l in range(self._retrycnt_at_a_time_sdb): + outs = check_output('sdb devices'.split(), timeout=10) + logging.debug(outs) + if find_pattern(pattern, outs): + logging.debug('found {}.'.format(self.get_id())) + return + time.sleep(0.2) + retry_attempt += 1 + else: + raise Exception('Can\'t find device.') + + def _detach_sdb(self): + """docstring for _detach_sdb""" + pass + + def _sdb_root_on(self): + """docstring for _sdb_root_on""" + call('sdb -s {} root on'.format(self.get_id()).split(), timeout=10) + time.sleep(0.5) + + def _acquire_global_lock(self): + """docstring for _acquire_global_lock""" + logging.debug('Try to acquire global lock...') + self._global_tlock.acquire() + self._global_ilock.acquire() + # set gid of ilock file + try: + os.chmod(self._global_ilock.path, 0o664) + except PermissionError: + logging.debug('Can\'t change lock file permission') + + if self._global_tlock.locked() and self._global_ilock.acquired: + logging.debug('global lock acquired for {}' + .format(self.get_id())) + + def _release_global_lock(self): + """docstring for _release_global_lock""" + if self._global_tlock.locked(): + self._global_tlock.release() + if self._global_ilock.acquired: + self._global_ilock.release() + logging.debug('global lock released') diff --git a/litmus/device/devicemock.py b/litmus/device/devicemock.py new file mode 100644 index 0000000..f927c68 --- /dev/null +++ b/litmus/device/devicemock.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import logging +from litmus.device.device import device +from litmus.core.util import check_output, find_pattern +from litmus.core.util import convert_single_item_to_list +from litmus.core.util import call + + +class devicemock(device): + """ + Litmus device class. + User can control device in topology by this class methods. + """ + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self._name = kwargs['deviceid'] + self._id = self._find_device_id() + + # init a cutter instance. + self._manager = kwargs['manager'] + + def _release(self): + """docstring for _release""" + pass + + def _find_device_id(self): + """docstring for _find_device_id""" + self.refresh_sdb_server() + outs = check_output(['sdb', 'devices'], timeout=10) + pattern = '.*List of devices attached \n([a-zA-Z0-9]*).*device.*' + found = find_pattern(pattern, outs, groupindex=1) + if found: + return found + + # public methods. + def get_id(self): + """ + Return the id of acquired device. + Device instance uses this id for sdb connection. + + Example: + >>> dut = mgr.acquire_dut('xu3') + >>> dut.get_id() + 'XU3_001' + + :returns str: device id + """ + return self._id + + def on(self, powercut_delay=2): + """ + Turn on the device acquired. + + :param float powercut_delay: power-cut delay for cutter + """ + logging.debug('turn on device {}'.format(self.get_name())) + + self.refresh_sdb_server() + if self._find_device_id() == self.get_id(): + self._sdb_root_on() + self.run_cmd(['reboot', '-f']) + time.sleep(60) + self.refresh_sdb_server() + self._sdb_root_on() + + def off(self, powercut_delay=2): + """ + Trun off the device acquired. + + :param float powercut_delay: power-cut delay for cutter + """ + logging.debug('turn off device {}'.format(self.get_name())) + + def thor(self, filenames): + """docstring for thor""" + cmd = 'lthor' + filenames = convert_single_item_to_list(filenames) + for l in filenames: + cmd += ' {}'.format(l) + logging.debug(cmd) + ret = call(cmd.split(), timeout=600) + if ret: + raise Exception('Thor error.') + + def heimdall(self, filenames, + partition_bin_mappings={'BOOT': 'zImage', + 'ROOTFS': 'rootfs.img', + 'USER': 'user.img', + 'SYSTEM-DATA': 'system-data.img'}): + """docstring for heimdall""" + filenames = convert_single_item_to_list(filenames) + tar_cmd = ['tar', 'xvfz'] + for l in filenames: + tar_cmd.append(l) + logging.debug(tar_cmd) + call(tar_cmd, timeout=30) + + heimdall_cmd = ['heimdall', 'flash'] + for key, elem in partition_bin_mappings.items(): + heimdall_cmd.append('--{}'.format(key)) + heimdall_cmd.append(elem) + logging.debug(heimdall_cmd) + + ret = call(heimdall_cmd, timeout=600) + if ret: + raise Exception('Heimdall error.') + + def flash(self, filenames, flasher='lthor', waiting=5, + partition_bin_mappings={'BOOT': 'zImage', + 'ROOTFS': 'rootfs.img', + 'USER': 'user.img', + 'SYSTEM-DATA': 'system-data.img'}): + """ + Flash binaries to device. + This function turn on device and turn off device automatically. + + :param dict filenames: filename string or dict + :param func flasher: wrapper function of external flashing tool + :param float waiting: waiting time to acquire cdc_acm device + :param dict partition_bin_mappings: partition table for device which use heimdall flasher + + Example: + >>> dut.flash(['boot.tar.gz','platform.tar.gz']) + >>> or + >>> dut.flash('platform.tar.gz') + + """ + logging.debug('flash binaries to device : {}'.format(filenames)) + + self.refresh_sdb_server() + + if not filenames: + raise Exception('There\'s no file to flash.') + try: + self._sdb_root_on() + self.run_cmd(['reboot', '-f', 'download'], timeout=10) + time.sleep(5) + if flasher == 'lthor': + self.thor(filenames=filenames) + elif flasher == 'heimdall': + self.heimdall(filenames=filenames, + partition_bin_mappings=partition_bin_mappings) + except (Exception, KeyboardInterrupt) as e: + logging.debug(e) + raise Exception('Can\'t flash files : {}.'.format(filenames)) + + def refresh_sdb_server(self): + """docstring for refresh_sdb_server""" + call('sdb kill-server; sdb start-server', shell=True, timeout=10) + time.sleep(1) diff --git a/litmus/device/deviceu3.py b/litmus/device/deviceu3.py new file mode 100644 index 0000000..9c5d399 --- /dev/null +++ b/litmus/device/deviceu3.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from litmus.device.device import device + + +class deviceu3(device): + """docstring for device""" + pass + diff --git a/litmus/device/devicexu3.py b/litmus/device/devicexu3.py new file mode 100644 index 0000000..14deefe --- /dev/null +++ b/litmus/device/devicexu3.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from litmus.device.device import device + + +class devicexu3(device): + """docstring for device""" + pass diff --git a/litmus/helper/__init__.py b/litmus/helper/__init__.py new file mode 100644 index 0000000..8a4f716 --- /dev/null +++ b/litmus/helper/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/litmus/helper/gt.py b/litmus/helper/gt.py new file mode 100644 index 0000000..a51a13f --- /dev/null +++ b/litmus/helper/gt.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import serial +import logging +from threading import Thread +from configparser import RawConfigParser +from litmus import _duts_ +from litmus.core.util import check_output, find_pattern, decode +from litmus.device.cuttercleware4 import cuttercleware4 +from litmus.device.cuttersmartpower import cuttersmartpower + + +class generate_topology_sdb_device(object): + """docstring for generate_topology_sdb_device""" + + devcatalog = [ + {'dev_type': 'u3', + 'cmd': 'printenv boardname', + 'pattern': r'.*odroidu3.*', + 'index': 1 + }, + {'dev_type': 'xu3', + 'cmd': 'printenv fdtfile', + 'pattern': r'.*odroidxu3.*', + 'index': 1 + }, + ] + + uarts = None + smartpowers = None + cleware4s = None + topology_path = _duts_ + open_mode = 'w+' + + def __init__(self, *args, **kwargs): + super(generate_topology_sdb_device, self).__init__() + if 'append' in kwargs and kwargs['append']: + self.open_mode = 'a+' + if 'topology' in kwargs and kwargs['topology']: + self.topology_path = kwargs['topology'] + + def init_smartpowers(self): + """docstring for init_smartpowers""" + def find_smartpower_names(): + """docstring for find_smartpowers""" + p = '.*Microchip Technology.*' + try: + smartpower_names = ['/dev/{}'.format(s) + for s in check_output('ls /dev | grep hidraw', + shell=True).split() + if find_pattern(p, check_output(['cat', + '/sys/class/hidraw/{}/device/uevent'.format(s)]))] + except AttributeError: + smartpower_names = [] + logging.debug('smart powers : {0}'.format(smartpower_names)) + return smartpower_names + + smartpower_names = find_smartpower_names() + self.smartpowers = [] + for l in smartpower_names: + obj = {'dev_id': '', + 'cutter_type': 'smartpower', + 'cutter_port': l + } + self.smartpowers.append(cuttersmartpower(**obj)) + + def init_cleware4s(self): + """docstring for init_cleware4s""" + def find_cleware4_names(): + """docstring for find_cleware4s""" + p = '.*Switch1.*version:.(29|512),.*serial number:.([0-9]{6,7})' + cleware4s = [find_pattern(p, s, groupindex=2) + for s in check_output('clewarecontrol -l', + shell=True).split('\n') + if find_pattern(p, s)] + logging.debug('cleware4 cutters : {0}'.format(cleware4s)) + return cleware4s + + cleware4_names = find_cleware4_names() + self.cleware4s = [] + for l in cleware4_names: + for idx in range(0, 4): + obj = {'dev_id': '', + 'cutter_type': 'cleware4', + 'cutter_port': l, + 'cleware_index': idx + } + self.cleware4s.append(cuttercleware4(**obj)) + + def open_uarts(self): + """docstring for open_uarts""" + + def init_jig(uart): + """docstring for init_jig""" + pass + + def get_items(): + """docstring for splitter""" + out = check_output('ls /dev | egrep "(ttyUSB|ttyS0)"', shell=True) + if out: + return out.split() + else: + raise Exception('There\'s no /dev/ttyUSB for duts.') + + def find_uart_names(): + """docstring for find_uarts""" + uarts = None + uarts = ['/dev/{}'.format(s) + for s in get_items()] + logging.debug('uarts : {0}'.format(uarts)) + return uarts + + self.uarts = [] + uart_names = find_uart_names() + for l in uart_names: + uart = serial.Serial(port=l, baudrate=115200, timeout=0.5) + init_jig(uart) + self.uarts.append(uart) + + def close_uarts(self): + """docstring for close_uarts""" + for l in self.uarts: + l.close() + + def enter_boot_prompt(self, uart, cnt): + """docstring for enter_boot_command""" + for l in range(cnt): + uart.write(b'\r') + time.sleep(0.025) + + def enter_bootloader_prompt_mode(self): + """docstring for enter_bootloader_prompt""" + + # create threads for entering bootloader prompt + delay = (5 + (len(self.cleware4s) * 2 * 4 + len(self.smartpowers) * 2 * 2)) * 30 + + threads = [] + for l in self.uarts: + + t = Thread(target=self.enter_boot_prompt, args=(l, delay)) + t.start() + threads.append(t) + + # turn on duts + self.turn_on_smartpowers() + self.turn_on_cleware4s() + + # join all threads + for l in threads: + l.join() + time.sleep(1) + + def turn_on(self, cutters): + """docstring for turn_on""" + for l in cutters: + l.off(0.5) + l.on(0.5) + + def turn_off(self, cutters): + """docstring for turn_off""" + for l in cutters: + l.off(0.5) + + def turn_on_smartpowers(self): + """docstring for turn_on_smartpowers""" + self.turn_on(self.smartpowers) + + def turn_off_smartpowers(self): + """docstring for turn_off_smartpowers""" + self.turn_off(self.smartpowers) + + def turn_on_cleware4s(self): + """docstring for turn_on_cleware4""" + self.turn_on(self.cleware4s) + + def turn_off_cleware4s(self): + """docstring for turn_off_cleware4""" + self.turn_off(self.cleware4s) + + def recognize_device(self, config, uart): + """docstring for recognize_device""" + for l in self.devcatalog: + logging.debug('Is {}'.format(l['dev_type'].upper())) + uart.flushInput() + time.sleep(0.1) + uart.flushOutput() + time.sleep(0.5) + uart.flush() + time.sleep(0.1) + + uart.write(l['cmd'].encode() + b'\r') + time.sleep(0.5) + + buf = uart.read(5000) + if find_pattern(l['pattern'], decode(buf)): + logging.debug('Yes') + name = '{0}_{1:0>3}'.format(l['dev_type'].upper(), + l['index']) + cfg = {'name': name, + 'dev_type': l['dev_type'], + 'uart_port': uart.name + } + l['index'] += 1 + return cfg + + def is_on(self, uart): + """docstring for is_on""" + p = r'.*echo.*' + uart.flushInput() + time.sleep(0.1) + uart.flushOutput() + time.sleep(0.1) + uart.flush() + time.sleep(0.1) + uart.write(b'echo\r') + time.sleep(0.1) + data = decode(b' '.join(uart.readlines(500))) + return find_pattern(p, data) + + def generate_device_topology(self): + """docstring for generate_device_topology""" + + # open config parser + config = RawConfigParser() + cfgs = [] + + # recognize device type + for l in self.uarts: + logging.debug('[Recognize device type for uart : {}]'.format(l.name)) + cfg = self.recognize_device(config, l) + if cfg: + cfgs.append(cfg) + else: + l.close() + + # remove closed uart obj + self.uarts = [m for m in self.uarts if m.isOpen()] + + logging.debug('[Generate topology configurations]') + for l in self.smartpowers: + l.off() + for l_uart in self.uarts: + if not self.is_on(l_uart): + dev = [m for m in cfgs if m['uart_port'] == l_uart.name][0] + dev['cutter_type'] = 'smartpower' + dev['cutter_port'] = l._cport + l_uart.close() + self.uarts.remove(l_uart) + logging.debug(dev) + break + + for l in self.cleware4s: + l.off() + for l_uart in self.uarts: + if not self.is_on(l_uart): + dev = [m for m in cfgs if m['uart_port'] == l_uart.name][0] + dev['cutter_type'] = 'cleware4' + dev['cutter_port'] = l._cport + dev['cleware_index'] = l._cindex + l_uart.close() + self.uarts.remove(l_uart) + logging.debug(dev) + break + + for l in self.uarts: + l.close() + + for l in cfgs: + section_name = l['name'] + l.pop('name') + config.add_section(section_name) + for key in sorted(l.keys()): + config.set(section_name, key, str(l[key])) + + with open(self.topology_path, self.open_mode) as f: + config.write(f) + logging.debug('Done.') + + def run(self): + """docstring for run""" + # init peripherals + self.init_smartpowers() + self.init_cleware4s() + self.open_uarts() + + # enter bootloader prompt + self.enter_bootloader_prompt_mode() + + # generate cfg + self.generate_device_topology() + + # turn off duts + self.turn_off_smartpowers() + self.turn_off_cleware4s() + + # close uarts + self.close_uarts() + + +def main(topology): + """docstring for main""" + try: + + logging.debug('# phase 1 : detect all devices which use sdb') + phase_sdb = generate_topology_sdb_device(topology=topology) + phase_sdb.run() + + except KeyboardInterrupt: + raise Exception('Keyboard Interrupt') + except Exception as e: + logging.debug(e) + raise Exception('Failed to generate topology') diff --git a/litmus/helper/helper.py b/litmus/helper/helper.py new file mode 100644 index 0000000..cd2012d --- /dev/null +++ b/litmus/helper/helper.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import sys +import time +import logging +import requests +import urllib.parse +from bs4 import BeautifulSoup +from litmus.core.util import find_pattern, find_all_pattern +from litmus.core.util import call + + +def tizen_snapshot_downloader(url, pattern_bin='tar.gz$', + username='', password='', + pattern_version='tizen[a-zA-Z0-9-_.^/]*[0-9]{8}.[0-9]{1,2}', + version=None, + timeout=10, + maxretry=20, + waiting_for_retry=10): + """ + Download snapshot images from web server. + + :param str url: url for downloading binaries. This has to include 'latest' string + :param str pattern_bin: filename pattern to find correct binary under the url + :param str username: username to access http server + :param str password: password to access http server + :param str pattern_version: pattern of tizen snapshot version string + :param str version: specific version string of tizen snapshot what you want to download + :param float timeout: timeout + :param int maxretry: max retry count to attempt the url for downloading + :param float waiting_for_retry: delay for each retry + + Example: + >>> from litmus.helper.helper import tizen_snapshot_downloader + >>> tizen_snapshot_downloader(url=\'http://download.tizen.org/snapshots/tizen/tv/latest/images/arm-wayland/tv-wayland-armv7l-odroidu3/\') + [\'tizen-tv_20160516.2_tv-wayland-armv7l-odroidu3.tar.gz\'] + + :returns list: filenames of downloaded binaries + + """ + + # convert latest url to actual url + url_to_find_latest_version_number = url.split('latest')[0] + + for loop in range(maxretry): + try: + f = requests.get(url_to_find_latest_version_number, + auth=(username, password), timeout=timeout) + if f.status_code == 200: + break + time.sleep(waiting_for_retry) + except requests.exceptions.Timeout as e: + logging.debug(e) + continue + except requests.exceptions.ConnectionError as e: + logging.debug(e) + continue + except Exception as e: + logging.debug(e) + raise Exception('Can\'t open url {0}'.format(url)) + else: + raise Exception('Can\'t open url {0}'.format(url)) + + latest_version = find_all_pattern(pattern_version, f.text)[-1] + url = url.replace('latest', latest_version) + + if version: + pattern_version_number = '[0-9]{8}.[0-9]{1,2}' + found = find_pattern(pattern_version_number, url) + url = url.replace(found, version) + + # get data from actual url and download binaries + for loop in range(maxretry): + try: + f = requests.get(url, auth=(username, password), timeout=timeout) + if f.status_code != 200: + continue + soup = BeautifulSoup(f.text, 'html.parser') + filenames = [] + + for l in soup.findAll('a', attrs={'href': re.compile(pattern_bin)}): + filename = l['href'] + fileurl = urllib.parse.urljoin(url, filename) + logging.debug(fileurl) + + with open(filename, 'wb') as f: + logging.debug('Downloading {}'.format(filename)) + resp = requests.get(fileurl, + auth=(username, password), + stream=True) + + total_length = resp.headers.get('Content-Length') + + if total_length is None: + f.write(resp.content) + else: + downloaded_data = 0 + total_length = int(total_length) + for download_data in resp.iter_content(chunk_size=1024 * 1024): + downloaded_data += len(download_data) + f.write(download_data) + done = int(50 * downloaded_data / total_length) + sys.stdout.write('\r[{0}{1}]'.format('#'*done, + ' '*(50-done))) + sys.stdout.flush() + logging.debug('') + filenames.append(filename) + + if filenames: + break + else: + logging.debug('There\'s no binary for downloading. Retry.') + time.sleep(waiting_for_retry) + + except requests.exceptions.Timeout as e: + logging.debug(e) + continue + except requests.exceptions.ConnectionError as e: + logging.debug(e) + continue + except Exception as e: + logging.debug(e) + raise Exception('Can\'t open url {0}'.format(url)) + else: + raise Exception('Can\'t open url {0}'.format(url)) + + return filenames + + +def install_plugin(dut, script, waiting=5, timeout=180): + """ + Install tizen plugins on device. + This helper function turn on device and turn off device automatically. + + :param device dut: device instance + :param str script: script path to install plugins on device + :param float waiting: wait time before installing plugins + :param float timeout: timeout + + Example: + >>> from litmus.helper.helper import install_plugin + >>> install_plugin(dut, + script='install-set/setup') + """ + + dut.on() + + script_path = '/'.join(script.split('/')[:-1]) + script_name = script.split('/')[-1] + + call('cp -R {0}/* .'.format(script_path), shell=True) + + time.sleep(waiting) + + call('sh {0} {1}'.format(script_name, dut.get_id()).split(), + timeout=timeout) + + dut.off() + + +import os +import shutil +from subprocess import DEVNULL + +def install_plugin_from_git(dut, url, branch, script, tmpdir='repo', + waiting=5, timeout=180, commitid=None): + """ + Clone a git project which include tizen plugins and install the plugins on device. + This helper function turn on device and turn off device automatically. + + :param device dut: device instance + :param str url: url for git project + :param str branch: branch name of the git project + :param str script: script path to install plugins on device + :param str tmpdir: temporary directory to clone the git project + :param float waiting: wait time before installing plugins + :param float timeout: timeout + :param str commitid: commitid which you want to clone + + .. note:: You have to configure your open-ssh key if you want to use ssh protocol to clone the git project. + + Example: + >>> from litmus.helper.helper import install_plugin_from_git + >>> install_plugin_from_git(dut, + url='ssh://{username}@localhost:29418/platform/adaptation/opengl-es-mali-t628' + branch='tizen_3.0' + script='install-set/setup') + + + """ + dut.on() + + call('git clone {0} {1} --branch {2}'.format(url, tmpdir, branch), + shell=True) + + if commitid: + call('git --git-dir={0}/.git checkout {1}'.format(tmpdir, commitid), + shell=True) + + call('find ./{0} -exec perl -pi -e "s/sdb\s+(-d\s+)*(root|shell|push|pull)/sdb -s {1} \\2/g" {{}} \;'.format(tmpdir, dut.get_id()), stderr=DEVNULL, shell=True) + call('find ./{0} -exec perl -pi -e "s/sdb\s+.*reboot.*//g" {{}} \;'.format(tmpdir), stderr=DEVNULL, shell=True) + + script = os.path.join(tmpdir, script) + + script_path = '/'.join(script.split('/')[:-1]) + script_name = script.split('/')[-1] + call('cp -R {0}/* .'.format(script_path), shell=True) + + time.sleep(waiting) + + call('sh {0}'.format(script_name).split(), timeout=timeout) + shutil.rmtree(tmpdir) + + dut.off() diff --git a/litmus/helper/tests.py b/litmus/helper/tests.py new file mode 100644 index 0000000..24e3531 --- /dev/null +++ b/litmus/helper/tests.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +import logging +import subprocess + + +def add_test_helper(dut, testcases): + """ + This function helps user add many tests from dict or yaml file to device. + + :param device dut: device instance + :param dict testcases: dict for test configuration + + Example: + >>> from litmus.core.util import load_yaml + >>> from litmus.helper.tests import add_test_helper + >>> testcases = load_yaml(\'tc.yaml\') + >>> add_test_helper(dut, testcases) + >>> dut.run_tests() + + tc.yaml example: + >>> testcases: + - + name: verify_process_is_running + from: litmus.helper.tests + result_dir : result + plan: + - + name: dbus_is_running + param: dbus + pattern: .*/usr/bin/dbus-daemon.* + - + name: enlightenment_is_running + param: enlightenment + pattern: .*/usr/bin/enlightenment.* + - + name: verify_dmesg + from: litmus.helper.tests + result_dir : result + plan: + - + name: panel_is_alive + param: panel + pattern: .*panel is dead.* + + """ + + for loop in testcases['testcases']: + fromstr = loop['from'] + name = loop['name'] + del loop['from'] + del loop['name'] + __import__(fromstr) + dut.add_test(getattr(sys.modules[fromstr], name), loop) + + +# pre-defined tests +def verify_process_is_running(dut, plan, result_dir): + """ + Check whether mandatory processes are running or not. + This testcase runs \'ps\' and \'grep\' commands on device. + + :param device dut: device instance + :param dict plan: test plan + :param str result_dir: directory to save test result xml + + Example: + >>> from litmus.helper.tests import verify_process_is_running + >>> verify_wifi_is_working(dut, + [{\'name\': \'dbus_is_running\', + \'param\': \'dbus\', + \'pattern\': \'.*/usr/bin/dbus-daemon.*\'}, + {\'name\': \'deviced_is_runing\', + \'param\': \'deviced\', + \'pattern\': \'.*/usr/bin/deviced.*\'}, + ], + \'result\') + + """ + test_name = 'verify_process_is_running' + template_report = """<report categ='SmokeTest' failures='{failure_cnt}' name='{test_name}'> +{data}</report>""" + template_test = """ <test executed='yes' name='{tc_name}'> + <result> + <success passed='{tc_result}' state='{tc_state}' /> + </result> + </test> +""" + + def _verify(item): + """docstring for _verify""" + cmd = ['ps', 'ax', '|', 'grep', item['param']] + res = dut.run_cmd(cmd) + p = re.compile(item['pattern']) + if p.search(res): + return True + else: + return False + + def _run(): + results = '' + failure_cnt = 0 + for item in plan: + tc_result = 'yes' if _verify(item) else 'no' + failure_cnt = failure_cnt+1 if tc_result != 'yes' else failure_cnt + dict_for_output = {'tc_name': item['name'], + 'tc_result': tc_result, + 'tc_state': 100 if tc_result == 'yes' else 0} + results += template_test.format(**dict_for_output) + output = template_report.format(failure_cnt=failure_cnt, + test_name=test_name, + data=results) + logging.debug(output) + return output + + def _save_result(result, result_dir): + """docstring for _save_result""" + with open(os.path.join(result_dir, 'testresult_process_is_running.xml'), 'w') as f: + f.write(result) + + _save_result(_run(), os.path.abspath(result_dir)) + + +def verify_dmesg(dut, plan, result_dir): + """ + Read kernel logs and check whether error log exists or not. + This testcase runs \'dmesg\' and \'grep\' commands on device. + + :param device dut: device instance + :param dict plan: test plan + :param str result_dir: directory to save test result xml + + Example: + >>> from litmus.helper.tests import verify_dmesg + >>> verify_dmesg(dut, + [{\'name\': \'panel_is_alive\', + \'param\': \'panel\', + \'pattern\': \'.*panel is dead.*\'}, + ], + \'result\') + + """ + test_name = 'verify_dmesg' + template_report = """<report categ='SmokeTest' failures='{failure_cnt}' name='{test_name}'> +{data}</report>""" + template_test = """ <test executed='yes' name='{tc_name}'> + <result> + <success passed='{tc_result}' state='{tc_state}' /> + </result> + </test> +""" + + def _verify(item): + """docstring for _verify""" + cmd = ['dmesg', '|', 'grep', item['param']] + res = dut.run_cmd(cmd) + p = re.compile(item['pattern']) + if not p.search(res): + return True + else: + return False + + def _run(): + results = '' + failure_cnt = 0 + for item in plan: + tc_result = 'yes' if _verify(item) else 'no' + failure_cnt = failure_cnt+1 if tc_result != 'yes' else failure_cnt + dict_for_output = {'tc_name': item['name'], + 'tc_result': tc_result, + 'tc_state': 100 if tc_result == 'yes' else 0} + results += template_test.format(**dict_for_output) + output = template_report.format(failure_cnt=failure_cnt, + test_name=test_name, + data=results) + logging.debug(output) + return output + + def _save_result(result, result_dir): + """docstring for _save_result""" + with open(os.path.join(result_dir, 'testresult_dmesg.xml'), 'w') as f: + f.write(result) + + _save_result(_run(), os.path.abspath(result_dir)) + + +import time +import queue +from threading import Thread +from litmus.core.util import convert_single_item_to_list + + +def verify_wifi_is_working(dut, wifi_apname, wifi_password, result_dir): + """ + Try to connect wifi ap and publish the test result as a xml file. + This testcase runs 'wifi_test' command on device. + + :param device dut: device instance + :param str wifi_apname: wifi ap name + :param str wifi_password: wifi ap password + :param str result_dir: directory to save test result xml + + Example: + >>> from litmus.helper.tests import verify_wifi_is_working + >>> verify_wifi_is_working(dut, 'setup', '', 'result') + + """ + test_name = 'wifi_is_working' + template_report = """<report categ='SmokeTest' failures='{failure_cnt}' name='{test_name}'> +{data}</report>""" + template_test = """ <test executed='yes' name='{tc_name}'> + <result> + <success passed='{tc_result}' state='{tc_state}' /> + </result> + </test> +""" + + def _enqueue_output(out, queue): + for line in iter(out.readline, b''): + queue.put(line.strip().decode()) + out.close() + + def _write_cmd(cmd, status_pass, status_fail, timeout=10): + """docstring for _write_cmd""" + status_pass = convert_single_item_to_list(status_pass) + status_fail = convert_single_item_to_list(status_fail) + + logging.debug('===== cmd : {} ====='.format(cmd)) + cmd = cmd + '\r' + start_time = time.perf_counter() + sdbshell.stdin.write(cmd.encode()) + sdbshell.stdin.flush() + time.sleep(0.5) + logging.debug('response:') + while True: + try: + line = q.get(timeout=0.1) + logging.debug(line) + except queue.Empty: + wait_time = time.perf_counter() - start_time + if wait_time > timeout: + raise Exception('timeout') + elif wait_time > (timeout / 2): + sdbshell.stdin.write('\r'.encode()) + sdbshell.stdin.flush() + time.sleep(1) + else: + if line in status_pass: + break + elif line in status_fail: + raise Exception('wifi test return fail : {}'.format(line)) + + def _run(): + """docstring for _run""" + + try: + _write_cmd('wifi_test; exit', 'Test Thread created...', None) + _write_cmd('1', 'Operation succeeded!', 'Operation failed!') + _write_cmd('3', + ['Success to activate Wi-Fi device', + 'Wi-Fi Activation Succeeded', + 'Fail to activate Wi-Fi device [ALREADY_EXISTS]'], + None) + time.sleep(7) + _write_cmd('9', 'Operation succeeded!', 'Operation failed!') + time.sleep(3) + for loop in range(3): + _write_cmd('b', 'Get AP list finished', None) + time.sleep(3) + _write_cmd('c', + 'Input a part of AP name to connect :', + ['Wi-Fi Activation Failed! error : OPERATION_FAILED', + 'Device state changed callback, state : Deactivated', + 'Operation failed!']) + _write_cmd(wifi_apname, + ['Passphrase required : TRUE', + 'Passphrase required : FALSE'], + ['Wi-Fi Activation Failed! error : OPERATION_FAILED', + 'Device state changed callback, state : Deactivated', + 'Operation failed!']) + if wifi_password and wifi_password != '': + _write_cmd(wifi_password, + 'Connection step finished', + ['Wi-Fi Activation Failed! error : OPERATION_FAILED', + 'Device state changed callback, state : Deactivated', + 'Operation failed!']) + _write_cmd('6', + ['Success to get connection state : Connected', + 'Wi-Fi Connection Succeeded'], + ['Wi-Fi Activation Failed! error : OPERATION_FAILED', + 'Wi-Fi Connection Failed! error : INVALID_KEY', + 'Device state changed callback, state : Deactivated', + 'Operation failed!', + 'Success to get connection state : Disconnected', + 'Connection state changed callback, state : Disconnected, AP name : {}'.format(wifi_apname)]) + _write_cmd('0', 'exit', None) + + dict_for_output = {'tc_name': test_name, + 'tc_result': 'yes', + 'tc_state': 100} + results = template_test.format(**dict_for_output) + output = template_report.format(failure_cnt=0, + test_name=test_name, + data=results) + except Exception: + dict_for_output = {'tc_name': test_name, + 'tc_result': 'no', + 'tc_state': 0} + results = template_test.format(**dict_for_output) + output = template_report.format(failure_cnt=1, + test_name=test_name, + data=results) + finally: + sdbshell.terminate() + return output + + def _save_result(result, result_dir): + """docstring for _save_result""" + logging.debug(result) + with open(os.path.join(result_dir, 'testresult_wifi.xml'), 'w') as f: + f.write(result) + + sdbshell = subprocess.Popen(['sdb', '-s', dut.get_id(), 'shell'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + q = queue.Queue() + t = Thread(target=_enqueue_output, args=(sdbshell.stdout, q)) + t.daemon = True + t.start() + _save_result(_run(), os.path.abspath(result_dir)) diff --git a/litmus/templates/empty/__init__.py b/litmus/templates/empty/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/litmus/templates/empty/__init__.py diff --git a/litmus/templates/empty/userscript.py b/litmus/templates/empty/userscript.py new file mode 100755 index 0000000..b98127d --- /dev/null +++ b/litmus/templates/empty/userscript.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from litmus.core.manager import manager + + +def main(*args, **kwargs): + """docstring for main""" + + # init manager instance + mgr = manager(*args, **kwargs) + + # init working directory + mgr.init_workingdir() diff --git a/litmus/templates/mock/__init__.py b/litmus/templates/mock/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/litmus/templates/mock/__init__.py diff --git a/litmus/templates/mock/conf.yaml b/litmus/templates/mock/conf.yaml new file mode 100644 index 0000000..072e0d7 --- /dev/null +++ b/litmus/templates/mock/conf.yaml @@ -0,0 +1,4 @@ +binary_urls: + - http://download.tizen.org/snapshots/tizen/mobile/latest/images/target-TM1/mobile-wayland-armv7l-tm1/ +username: <username> +password: <password> diff --git a/litmus/templates/mock/tc.yaml b/litmus/templates/mock/tc.yaml new file mode 100644 index 0000000..44ed8a2 --- /dev/null +++ b/litmus/templates/mock/tc.yaml @@ -0,0 +1,23 @@ +testcases: + - name: verify_process_is_running + from: litmus.helper.tests + result_dir: result + plan: + - name: enlightenment_is_running + param: enlightenment + pattern: .*/usr/bin/enlightenment.* + - name: deviced_is_running + param: deviced + pattern: .*/usr/bin/deviced.* + - name: pulseaudio_is_running + param: pulseaudio + pattern: .*/usr/bin/pulseaudio.* + - name: sdbd_is_running + param: sdbd + pattern: .*/usr/sbin/sdbd.* + - name: alarm-server_is_running + param: alarm-server + pattern: .*/usr/bin/alarm-server.* + - name: media-server_is_running + param: media-server + pattern: .*/usr/bin/media-server.* diff --git a/litmus/templates/mock/userscript.py b/litmus/templates/mock/userscript.py new file mode 100755 index 0000000..2abe0c9 --- /dev/null +++ b/litmus/templates/mock/userscript.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +from litmus.core.util import load_yaml +from litmus.core.manager import manager +from litmus.helper.helper import tizen_snapshot_downloader as downloader +from litmus.helper.tests import add_test_helper + + +def main(*args, **kwargs): + + # init manager instance + mgr = manager(*args, **kwargs) + + # init working directory + mgr.init_workingdir() + + # get projectinfo + project_info = load_yaml('conf.yaml') + + username = project_info['username'] + password = project_info['password'] + binary_urls = project_info['binary_urls'] + + # get version from parameter + try: + version = kwargs['param'][0] + except (IndexError, TypeError): + version = None + + # download binaries from snapshot download server + filenames = [] + for url in binary_urls: + filenames.extend(downloader(url=url, + username=username, + password=password, + version=version)) + + # get an available device for testing. + dut = mgr.acquire_dut('mock', max_retry_times=180) + + # flashing binaries to device. + dut.flash(filenames) + + # turn on dut. + dut.on() + + # run helper functions for testing. + if not os.path.exists('result'): + os.mkdir('result') + + testcases = load_yaml('tc.yaml') + add_test_helper(dut, testcases) + dut.run_tests() + + # turn off dut. + dut.off() + + # release a device + mgr.release_dut(dut) diff --git a/litmus/templates/u3/__init__.py b/litmus/templates/u3/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/litmus/templates/u3/__init__.py diff --git a/litmus/templates/u3/conf.yaml b/litmus/templates/u3/conf.yaml new file mode 100644 index 0000000..9a76742 --- /dev/null +++ b/litmus/templates/u3/conf.yaml @@ -0,0 +1,5 @@ +binary_urls: + - http://download.tizen.org/snapshots/tizen/tv/latest/images/arm-wayland/tv-wayland-armv7l-odroidu3/ + - http://download.tizen.org/snapshots/tizen/tv/latest/images/arm-wayland/tv-boot-armv7l-odroidu3/ +username: <username> +password: <password> diff --git a/litmus/templates/u3/tc.yaml b/litmus/templates/u3/tc.yaml new file mode 100644 index 0000000..44ed8a2 --- /dev/null +++ b/litmus/templates/u3/tc.yaml @@ -0,0 +1,23 @@ +testcases: + - name: verify_process_is_running + from: litmus.helper.tests + result_dir: result + plan: + - name: enlightenment_is_running + param: enlightenment + pattern: .*/usr/bin/enlightenment.* + - name: deviced_is_running + param: deviced + pattern: .*/usr/bin/deviced.* + - name: pulseaudio_is_running + param: pulseaudio + pattern: .*/usr/bin/pulseaudio.* + - name: sdbd_is_running + param: sdbd + pattern: .*/usr/sbin/sdbd.* + - name: alarm-server_is_running + param: alarm-server + pattern: .*/usr/bin/alarm-server.* + - name: media-server_is_running + param: media-server + pattern: .*/usr/bin/media-server.* diff --git a/litmus/templates/u3/userscript.py b/litmus/templates/u3/userscript.py new file mode 100755 index 0000000..a965801 --- /dev/null +++ b/litmus/templates/u3/userscript.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +from litmus.core.util import load_yaml +from litmus.core.manager import manager +from litmus.helper.helper import tizen_snapshot_downloader as downloader +from litmus.helper.tests import add_test_helper + + +def main(*args, **kwargs): + + # init manager instance + mgr = manager(*args, **kwargs) + + # init working directory + mgr.init_workingdir() + + # get projectinfo + project_info = load_yaml('conf.yaml') + + username = project_info['username'] + password = project_info['password'] + binary_urls = project_info['binary_urls'] + + # get version from parameter + try: + version = kwargs['param'][0] + except (IndexError, TypeError): + version = None + + # download binaries from snapshot download server + filenames = [] + for url in binary_urls: + filenames.extend(downloader(url=url, + username=username, + password=password, + version=version)) + + # get an available device for testing. + dut = mgr.acquire_dut('u3', max_retry_times=180) + + # flashing binaries to device. + dut.flash(filenames) + + # turn on dut. + dut.on() + + # run helper functions for testing. + if not os.path.exists('result'): + os.mkdir('result') + + testcases = load_yaml('tc.yaml') + add_test_helper(dut, testcases) + dut.run_tests() + + # turn off dut. + dut.off() + + # release a device + mgr.release_dut(dut) diff --git a/litmus/templates/xu3/__init__.py b/litmus/templates/xu3/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/litmus/templates/xu3/__init__.py diff --git a/litmus/templates/xu3/conf.yaml b/litmus/templates/xu3/conf.yaml new file mode 100644 index 0000000..b218ccd --- /dev/null +++ b/litmus/templates/xu3/conf.yaml @@ -0,0 +1,5 @@ +binary_urls: + - http://download.tizen.org/snapshots/tizen/tv/latest/images/arm-wayland/tv-wayland-armv7l-odroidu3/ + - http://download.tizen.org/snapshots/tizen/tv/latest/images/arm-wayland/tv-boot-armv7l-odroidxu3/ +username: <username> +password: <password> diff --git a/litmus/templates/xu3/tc.yaml b/litmus/templates/xu3/tc.yaml new file mode 100644 index 0000000..44ed8a2 --- /dev/null +++ b/litmus/templates/xu3/tc.yaml @@ -0,0 +1,23 @@ +testcases: + - name: verify_process_is_running + from: litmus.helper.tests + result_dir: result + plan: + - name: enlightenment_is_running + param: enlightenment + pattern: .*/usr/bin/enlightenment.* + - name: deviced_is_running + param: deviced + pattern: .*/usr/bin/deviced.* + - name: pulseaudio_is_running + param: pulseaudio + pattern: .*/usr/bin/pulseaudio.* + - name: sdbd_is_running + param: sdbd + pattern: .*/usr/sbin/sdbd.* + - name: alarm-server_is_running + param: alarm-server + pattern: .*/usr/bin/alarm-server.* + - name: media-server_is_running + param: media-server + pattern: .*/usr/bin/media-server.* diff --git a/litmus/templates/xu3/userscript.py b/litmus/templates/xu3/userscript.py new file mode 100755 index 0000000..3723505 --- /dev/null +++ b/litmus/templates/xu3/userscript.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +from litmus.core.util import load_yaml +from litmus.core.manager import manager +from litmus.helper.helper import tizen_snapshot_downloader as downloader +from litmus.helper.tests import add_test_helper + + +def main(*args, **kwargs): + + # init manager instance + mgr = manager(*args, **kwargs) + + # init working directory + mgr.init_workingdir() + + # get projectinfo + project_info = load_yaml('conf.yaml') + + username = project_info['username'] + password = project_info['password'] + binary_urls = project_info['binary_urls'] + + # get version from parameter + try: + version = kwargs['param'][0] + except (IndexError, TypeError): + version = None + + # download binaries from snapshot download server + filenames = [] + for url in binary_urls: + filenames.extend(downloader(url=url, + username=username, + password=password, + version=version)) + + # get an available device for testing. + dut = mgr.acquire_dut('xu3', max_retry_times=180) + + # flashing binaries to device. + dut.flash(filenames) + + # turn on dut. + dut.on() + + # run helper functions for testing. + if not os.path.exists('result'): + os.mkdir('result') + + testcases = load_yaml('tc.yaml') + add_test_helper(dut, testcases) + dut.run_tests() + + # turn off dut. + dut.off() + + # release a device + mgr.release_dut(dut) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2c22612 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import os +from setuptools import setup, find_packages + +PROJECT_NAME = 'litmus' + +version = re.search("__version__.*'(.+)'", + open(os.path.join(PROJECT_NAME, '__init__.py')) + .read()).group(1) + +setup(name=PROJECT_NAME, + description='Lightweight test manager', + long_description='Lightweight test manager for tizen automated testing', + version=version, + author="Donghoon Shin", + author_email="dhs.shin@samsung.com", + url="http://www.tizen.org", + package_dir={PROJECT_NAME: 'litmus'}, + packages=find_packages(exclude=['litmus.templates']), + include_package_data=True, + license="Apache", + platforms='any', + scripts=['tools/litmus'], + ) diff --git a/tests/test_manager.py b/tests/test_manager.py new file mode 100644 index 0000000..087f32b --- /dev/null +++ b/tests/test_manager.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import os +import unittest +from litmus.core.manager import manager + + +class TestLitmus(unittest.TestCase): + + mgr = None + + def setUp(self): + self.mgr = manager() + + def tearDown(self): + self.mgr.release_dut() + + def test_acquisition(self): + dut = dut1 = None + try: + dut = self.mgr.acquire_dut('xu3') + except Exception as e: + print(e) + + self.assertNotEqual(first=dut, second=None) + + try: + dut1 = self.mgr.acquire_dut('hawkp') + except Exception as e: + print(e) + + self.assertEqual(first=dut1, second=None) + + def test_all_acquired_duts(self): + self.mgr.release_dut() + + self.assertEqual(first=self.mgr.get_all_acquired_duts(), second=[]) + + try: + self.mgr.acquire_dut('xu3') + except Exception as e: + print(e) + + self.assertNotEqual(first=self.mgr.get_all_acquired_duts(), second=[]) + + def test_workingdir(self): + self.mgr.init_workingdir(workingdir='.') + current_dir = os.path.abspath(os.path.curdir) + + self.assertEqual(first=current_dir, second=self.mgr.get_workingdir()) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/tools/litmus b/tools/litmus new file mode 100644 index 0000000..ef8483b --- /dev/null +++ b/tools/litmus @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 Samsung Electronics Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import signal +import functools +import logging +import traceback +from argparse import ArgumentParser, RawTextHelpFormatter +from litmus import __version__, _path_for_locks_, _duts_, _projects_, _confdir_ +from litmus.core.util import init_logger + + +def sigterm_handler(signal, frame): + """docstring for sigterm_handler""" + raise Exception('SIGTERM') + sys.exit(1) + + +def subparser(func): + """docstring for subparser""" + @functools.wraps(func) + def wrapper(parser): + """docstring for wrapper""" + splitted = func.__doc__.split('\n') + name = func.__name__.split('_')[0] + subparser = parser.add_parser(name, help=splitted[0], + description='\n'.join(splitted[1:]), + formatter_class=RawTextHelpFormatter) + subparser.set_defaults(module='cmd_{0}'.format(name)) + return func(subparser) + return wrapper + + +@subparser +def adhoc_parser(parser): + """run a adhoc script + Examples: + $ litmus adhoc <project_path> + """ + parser.add_argument('project_path', type=str, help='project path') + parser.add_argument('-p', '--param', type=str, nargs='*', + help='parameters for project') + parser.add_argument('-d', '--workingdir', type=str, + help='working directory') + return parser + + +@subparser +def mk_parser(parser): + """make a new litmus project + Examples: + $ litmus mk <project_name> + """ + parser.add_argument('project', type=str, help='project name') + parser.add_argument('-t', '--type', type=str, help='dut type') + parser.add_argument('-d', '--description', type=str, help='description') + return parser + + +@subparser +def rm_parser(parser): + """remove a litmus project + Examples: + $ litmus rm <project_name> + """ + parser.add_argument('project', type=str, help='project name') + return parser + + +@subparser +def run_parser(parser): + """run a litmus project + Examples: + $ litmus run <project_name> + """ + parser.add_argument('project', type=str, help='project name') + parser.add_argument('-p', '--param', type=str, nargs='*', + help='parameters for project') + parser.add_argument('-d', '--workingdir', type=str, + help='working directory') + return parser + + +@subparser +def ls_parser(parser): + """list all litmus projects + Examples: + $ litmus ls + """ + return parser + + +@subparser +def dev_parser(parser): + """list all devices from topology configuration + Examples: + $ litmus dev + """ + return parser + + +@subparser +def gt_parser(parser): + """generate a topology configuration + Examples: + $ litmus gt + """ + return parser + + +@subparser +def cp_parser(parser): + """copy a litmus project + Examples: + $ litmus cp <origin project name> <new project name> + """ + parser.add_argument('orig', type=str, help='origin project name') + parser.add_argument('new', type=str, help='new project name') + parser.add_argument('-d', '--description', type=str, help='description') + return parser + + +@subparser +def imp_parser(parser): + """import a litmus project + Examples: + $ litmus imp <project name> + """ + parser.add_argument('project', type=str, help='project name') + parser.add_argument('-d', '--description', type=str, help='description') + parser.add_argument('-p', '--path', type=str, help='path') + return parser + + +def init_lockdir(): + """docstring for init_lockdir""" + if not os.path.exists(_path_for_locks_): + os.mkdir(_path_for_locks_) + try: + os.chmod(_path_for_locks_, 0o775) + except PermissionError: + logging.debug('Can\'t change lock directory permission') + + +def init_confdir(): + """docstring for init_confdir""" + if not os.path.exists(_confdir_): + os.mkdir(_confdir_) + try: + os.chmod(_confdir_, 0o775) + except PermissionError: + logging.debug('Can\'t change config directory permission') + + if not os.path.exists(_duts_): + open(_duts_, 'a').close() + try: + os.chmod(_duts_, 0o664) + except PermissionError: + logging.debug('Can\'t change topology file permission') + + if not os.path.exists(_projects_): + open(_projects_, 'a').close() + try: + os.chmod(_projects_, 0o664) + except PermissionError: + logging.debug('Can\'t change projects file permission') + + +def main(argv=None): + """docstring for main""" + description = 'litmus : lightweight test manager' + parser = ArgumentParser(description=description) + + parser.add_argument('-V', '--version', + action='version', + version='%(prog)s ' + __version__) + parser.add_argument('-t', '--topology', + type=str, + help='topology file path') + parser.add_argument('-p', '--projects', + type=str, + help='projects file path') + + parser.format_usage = parser.format_help + subparsers = parser.add_subparsers(title='subcommands', dest='subcommands') + subparsers.required = True + + for name, obj in sorted(globals().items()): + if name.endswith('_parser') and callable(obj): + obj(subparsers) + + args = parser.parse_args(argv[1:]) + + if not args.projects: + args.projects = _projects_ + else: + args.projects = os.path.expanduser(args.projects) + if not args.topology: + args.topology = _duts_ + else: + args.topology = os.path.expanduser(args.topology) + + module = __import__('litmus.cmds.{0}'.format(args.module), + fromlist=[args.module]) + return module.main(args) + + +if __name__ == '__main__': + try: + init_logger() + init_lockdir() + init_confdir() + signal.signal(signal.SIGTERM, sigterm_handler) + sys.exit(main(sys.argv)) + except KeyboardInterrupt: + raise Exception('KeyboardInterrupt') + except Exception: + logging.debug(traceback.format_exc()) + sys.exit(1) |