Update initial source codes 68/88468/2
authorDonghoon Shin <dhs.shin@samsung.com>
Mon, 19 Sep 2016 08:25:33 +0000 (17:25 +0900)
committerDonghoon Shin <dhs.shin@samsung.com>
Mon, 19 Sep 2016 08:33:03 +0000 (17:33 +0900)
Change-Id: Ic758f09a4cd2edddd3d714a251ed72ded3e66f0e

68 files changed:
AUTHORS [new file with mode: 0644]
CHANGES.txt [new file with mode: 0644]
LICENSE.APLv2 [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/litmus.udev [new file with mode: 0644]
debian/postinst [new file with mode: 0644]
debian/postrm [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
docs/Makefile [new file with mode: 0644]
docs/source/conf.py [new file with mode: 0644]
docs/source/index.rst [new file with mode: 0644]
docs/source/litmus.core.rst [new file with mode: 0644]
docs/source/litmus.device.rst [new file with mode: 0644]
docs/source/litmus.helper.rst [new file with mode: 0644]
docs/source/litmus.rst [new file with mode: 0644]
docs/source/modules.rst [new file with mode: 0644]
litmus/__init__.py [new file with mode: 0644]
litmus/cmds/__init__.py [new file with mode: 0644]
litmus/cmds/cmd_adhoc.py [new file with mode: 0755]
litmus/cmds/cmd_cp.py [new file with mode: 0755]
litmus/cmds/cmd_dev.py [new file with mode: 0755]
litmus/cmds/cmd_gt.py [new file with mode: 0755]
litmus/cmds/cmd_imp.py [new file with mode: 0755]
litmus/cmds/cmd_ls.py [new file with mode: 0755]
litmus/cmds/cmd_mk.py [new file with mode: 0755]
litmus/cmds/cmd_rm.py [new file with mode: 0755]
litmus/cmds/cmd_run.py [new file with mode: 0755]
litmus/core/__init__.py [new file with mode: 0644]
litmus/core/exceptions.py [new file with mode: 0644]
litmus/core/manager.py [new file with mode: 0644]
litmus/core/util.py [new file with mode: 0644]
litmus/device/__init__.py [new file with mode: 0644]
litmus/device/cutter.py [new file with mode: 0644]
litmus/device/cuttercleware4.py [new file with mode: 0644]
litmus/device/cuttersmartpower.py [new file with mode: 0644]
litmus/device/device.py [new file with mode: 0644]
litmus/device/devicemock.py [new file with mode: 0644]
litmus/device/deviceu3.py [new file with mode: 0644]
litmus/device/devicexu3.py [new file with mode: 0644]
litmus/helper/__init__.py [new file with mode: 0644]
litmus/helper/gt.py [new file with mode: 0644]
litmus/helper/helper.py [new file with mode: 0644]
litmus/helper/tests.py [new file with mode: 0644]
litmus/templates/empty/__init__.py [new file with mode: 0644]
litmus/templates/empty/userscript.py [new file with mode: 0755]
litmus/templates/mock/__init__.py [new file with mode: 0644]
litmus/templates/mock/conf.yaml [new file with mode: 0644]
litmus/templates/mock/tc.yaml [new file with mode: 0644]
litmus/templates/mock/userscript.py [new file with mode: 0755]
litmus/templates/u3/__init__.py [new file with mode: 0644]
litmus/templates/u3/conf.yaml [new file with mode: 0644]
litmus/templates/u3/tc.yaml [new file with mode: 0644]
litmus/templates/u3/userscript.py [new file with mode: 0755]
litmus/templates/xu3/__init__.py [new file with mode: 0644]
litmus/templates/xu3/conf.yaml [new file with mode: 0644]
litmus/templates/xu3/tc.yaml [new file with mode: 0644]
litmus/templates/xu3/userscript.py [new file with mode: 0755]
setup.py [new file with mode: 0644]
tests/test_manager.py [new file with mode: 0644]
tools/litmus [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
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 (file)
index 0000000..d25eebd
--- /dev/null
@@ -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 (file)
index 0000000..5d51900
--- /dev/null
@@ -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 (file)
index 0000000..07fcd5a
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..c7a43e7
--- /dev/null
@@ -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 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..049fa9a
--- /dev/null
@@ -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 (file)
index 0000000..4647046
--- /dev/null
@@ -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 (file)
index 0000000..a18c7d9
--- /dev/null
@@ -0,0 +1,2 @@
+CHANGES.txt
+README.md
diff --git a/debian/litmus.udev b/debian/litmus.udev
new file mode 100644 (file)
index 0000000..2630965
--- /dev/null
@@ -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 (file)
index 0000000..65629a5
--- /dev/null
@@ -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 (file)
index 0000000..dd11900
--- /dev/null
@@ -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 (executable)
index 0000000..e090276
--- /dev/null
@@ -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 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..b39d736
--- /dev/null
@@ -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 (file)
index 0000000..5cc665f
--- /dev/null
@@ -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 (file)
index 0000000..7f110d2
--- /dev/null
@@ -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 (file)
index 0000000..4c82e86
--- /dev/null
@@ -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 (file)
index 0000000..067b857
--- /dev/null
@@ -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 (file)
index 0000000..1aa80eb
--- /dev/null
@@ -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 (file)
index 0000000..b8ed084
--- /dev/null
@@ -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 (file)
index 0000000..2363359
--- /dev/null
@@ -0,0 +1,7 @@
+litmus
+======
+
+.. toctree::
+   :maxdepth: 4
+
+   litmus
diff --git a/litmus/__init__.py b/litmus/__init__.py
new file mode 100644 (file)
index 0000000..724e475
--- /dev/null
@@ -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 (file)
index 0000000..4852a96
--- /dev/null
@@ -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 (executable)
index 0000000..a1de8e6
--- /dev/null
@@ -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 (executable)
index 0000000..efbe848
--- /dev/null
@@ -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 (executable)
index 0000000..fecc4d5
--- /dev/null
@@ -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 (executable)
index 0000000..b89cbe8
--- /dev/null
@@ -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 (executable)
index 0000000..ff59589
--- /dev/null
@@ -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 (executable)
index 0000000..4e96b2c
--- /dev/null
@@ -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 (executable)
index 0000000..40fa12c
--- /dev/null
@@ -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 (executable)
index 0000000..1321f9d
--- /dev/null
@@ -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 (executable)
index 0000000..6332d6f
--- /dev/null
@@ -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 (file)
index 0000000..8a4f716
--- /dev/null
@@ -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 (file)
index 0000000..c176ffa
--- /dev/null
@@ -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 (file)
index 0000000..332b8bc
--- /dev/null
@@ -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 (file)
index 0000000..7851bdb
--- /dev/null
@@ -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 (file)
index 0000000..8a4f716
--- /dev/null
@@ -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 (file)
index 0000000..aa5acd2
--- /dev/null
@@ -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 (file)
index 0000000..6d73ea3
--- /dev/null
@@ -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 (file)
index 0000000..231733a
--- /dev/null
@@ -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 (file)
index 0000000..9d9123a
--- /dev/null
@@ -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 (file)
index 0000000..f927c68
--- /dev/null
@@ -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 (file)
index 0000000..9c5d399
--- /dev/null
@@ -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 (file)
index 0000000..14deefe
--- /dev/null
@@ -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 (file)
index 0000000..8a4f716
--- /dev/null
@@ -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 (file)
index 0000000..a51a13f
--- /dev/null
@@ -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 (file)
index 0000000..cd2012d
--- /dev/null
@@ -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 (file)
index 0000000..24e3531
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/litmus/templates/empty/userscript.py b/litmus/templates/empty/userscript.py
new file mode 100755 (executable)
index 0000000..b98127d
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/litmus/templates/mock/conf.yaml b/litmus/templates/mock/conf.yaml
new file mode 100644 (file)
index 0000000..072e0d7
--- /dev/null
@@ -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 (file)
index 0000000..44ed8a2
--- /dev/null
@@ -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 (executable)
index 0000000..2abe0c9
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/litmus/templates/u3/conf.yaml b/litmus/templates/u3/conf.yaml
new file mode 100644 (file)
index 0000000..9a76742
--- /dev/null
@@ -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 (file)
index 0000000..44ed8a2
--- /dev/null
@@ -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 (executable)
index 0000000..a965801
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/litmus/templates/xu3/conf.yaml b/litmus/templates/xu3/conf.yaml
new file mode 100644 (file)
index 0000000..b218ccd
--- /dev/null
@@ -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 (file)
index 0000000..44ed8a2
--- /dev/null
@@ -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 (executable)
index 0000000..3723505
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..087f32b
--- /dev/null
@@ -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 (file)
index 0000000..ef8483b
--- /dev/null
@@ -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)