summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--CHANGES.txt31
-rw-r--r--LICENSE.APLv2201
-rw-r--r--MANIFEST.in21
-rw-r--r--Makefile7
-rw-r--r--README.md32
-rw-r--r--debian/changelog37
-rw-r--r--debian/compat1
-rw-r--r--debian/control27
-rw-r--r--debian/copyright23
-rw-r--r--debian/docs2
-rw-r--r--debian/litmus.udev3
-rw-r--r--debian/postinst41
-rw-r--r--debian/postrm39
-rwxr-xr-xdebian/rules12
-rw-r--r--debian/source/format1
-rw-r--r--docs/Makefile223
-rw-r--r--docs/source/conf.py298
-rw-r--r--docs/source/index.rst22
-rw-r--r--docs/source/litmus.core.rst30
-rw-r--r--docs/source/litmus.device.rst21
-rw-r--r--docs/source/litmus.helper.rst30
-rw-r--r--docs/source/litmus.rst19
-rw-r--r--docs/source/modules.rst7
-rw-r--r--litmus/__init__.py24
-rw-r--r--litmus/cmds/__init__.py29
-rwxr-xr-xlitmus/cmds/cmd_adhoc.py32
-rwxr-xr-xlitmus/cmds/cmd_cp.py52
-rwxr-xr-xlitmus/cmds/cmd_dev.py29
-rwxr-xr-xlitmus/cmds/cmd_gt.py21
-rwxr-xr-xlitmus/cmds/cmd_imp.py65
-rwxr-xr-xlitmus/cmds/cmd_ls.py27
-rwxr-xr-xlitmus/cmds/cmd_mk.py61
-rwxr-xr-xlitmus/cmds/cmd_rm.py35
-rwxr-xr-xlitmus/cmds/cmd_run.py34
-rw-r--r--litmus/core/__init__.py14
-rw-r--r--litmus/core/exceptions.py17
-rw-r--r--litmus/core/manager.py264
-rw-r--r--litmus/core/util.py187
-rw-r--r--litmus/device/__init__.py14
-rw-r--r--litmus/device/cutter.py59
-rw-r--r--litmus/device/cuttercleware4.py63
-rw-r--r--litmus/device/cuttersmartpower.py88
-rw-r--r--litmus/device/device.py545
-rw-r--r--litmus/device/devicemock.py167
-rw-r--r--litmus/device/deviceu3.py22
-rw-r--r--litmus/device/devicexu3.py21
-rw-r--r--litmus/helper/__init__.py14
-rw-r--r--litmus/helper/gt.py326
-rw-r--r--litmus/helper/helper.py228
-rw-r--r--litmus/helper/tests.py348
-rw-r--r--litmus/templates/empty/__init__.py0
-rwxr-xr-xlitmus/templates/empty/userscript.py12
-rw-r--r--litmus/templates/mock/__init__.py0
-rw-r--r--litmus/templates/mock/conf.yaml4
-rw-r--r--litmus/templates/mock/tc.yaml23
-rwxr-xr-xlitmus/templates/mock/userscript.py59
-rw-r--r--litmus/templates/u3/__init__.py0
-rw-r--r--litmus/templates/u3/conf.yaml5
-rw-r--r--litmus/templates/u3/tc.yaml23
-rwxr-xr-xlitmus/templates/u3/userscript.py59
-rw-r--r--litmus/templates/xu3/__init__.py0
-rw-r--r--litmus/templates/xu3/conf.yaml5
-rw-r--r--litmus/templates/xu3/tc.yaml23
-rwxr-xr-xlitmus/templates/xu3/userscript.py59
-rw-r--r--setup.py39
-rw-r--r--tests/test_manager.py54
-rw-r--r--tools/litmus233
68 files changed, 4513 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..fb51644
--- /dev/null
+++ b/AUTHORS
@@ -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)