mirror of
https://gitee.com/bianbu-linux/factorytest
synced 2025-04-18 19:34:59 -04:00
Update for v1.0beta4
This commit is contained in:
commit
91a5524b15
71 changed files with 6451 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
__pycache__/
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
工厂测试源码。
|
||||
|
||||
- 支持图形界面
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python3
|
||||
- unittest
|
||||
- PyQt5
|
||||
|
||||
## 源码目录
|
||||
|
||||
```
|
||||
├── cricket # Cricket is a graphical tool that helps you run your test suites.
|
||||
├── gui-main
|
||||
└── tests # factory test case
|
||||
├── auto # auto test case
|
||||
└── manual # manual test case
|
||||
```
|
||||
|
||||
## 测试项
|
||||
|
||||
测试项有自动测试项和手动测试项两类。自动测试项无需人工干预,自动判定测试是否通过。手动测试需要人工参与,判断是否通过。
|
||||
|
||||
添加测试项规则:
|
||||
|
||||
- 自动测试项和手动测试项分别添加到`tests/auto`和`tests/manual`目录
|
||||
- 每个模块一个文件,以`test_`开头,文件里定义一个类,继承`unittest.TestCase`
|
||||
- 测试项为类的方法,方法名以`test_`开头
|
||||
- 类里定义一个字典`LANGUAGES`,用于支持多国语音
|
||||
|
||||
## 多国语言
|
||||
|
||||
## TODO
|
||||
|
||||
- 支持命令行
|
18
cricket/AUTHORS
Normal file
18
cricket/AUTHORS
Normal file
|
@ -0,0 +1,18 @@
|
|||
Cricket was originally created in January 2013.
|
||||
|
||||
The PRIMARY AUTHORS are (and/or have been):
|
||||
Russell Keith-Magee
|
||||
|
||||
And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
|
||||
people who have submitted patches, reported bugs, added translations, helped
|
||||
answer newbie questions, and generally made Cricket that much better:
|
||||
|
||||
Richard Jones <richard@mechanicalcat.net>
|
||||
Ben Khoo (khoobks)
|
||||
Tennessee Leeuwenburg <tleeuwenburg@gmail.com>
|
||||
Curtin Maloney (funkybob)
|
||||
Stephen McDonald <steve@jupo.org>
|
||||
Simon Meers <simon@simonmeers.com>
|
||||
Thomas Sutton <me@thomas-sutton.id.au>
|
||||
Sebastian Vetter <sebastian@roadside-developer.com>
|
||||
Markus Zapke-Gründemann (keimlink)
|
27
cricket/LICENSE
Normal file
27
cricket/LICENSE
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013 Russell Keith-Magee.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Cricket nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
8
cricket/MANIFEST.in
Normal file
8
cricket/MANIFEST.in
Normal file
|
@ -0,0 +1,8 @@
|
|||
include AUTHORS
|
||||
include LICENSE
|
||||
include requirements*.txt
|
||||
recursive-include docs *.bat
|
||||
recursive-include docs *.py
|
||||
recursive-include docs *.rst
|
||||
recursive-include docs Makefile
|
||||
recursive-include tests *.py
|
135
cricket/PKG-INFO
Normal file
135
cricket/PKG-INFO
Normal file
|
@ -0,0 +1,135 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: cricket
|
||||
Version: 0.2.5
|
||||
Summary: A graphical tool to assist running test suites.
|
||||
Home-page: http://pybee.org/cricket
|
||||
Author: Russell Keith-Magee
|
||||
Author-email: russell@keith-magee.com
|
||||
License: New BSD
|
||||
Description: Cricket
|
||||
=======
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. The project website is `http://pybee.org/cricket`_.
|
||||
|
||||
Cricket is a graphical tool that helps you run your test suites.
|
||||
|
||||
Normal unittest test runners dump all output to the console, and provide very
|
||||
little detail while the suite is running. As a result:
|
||||
|
||||
* You can't start looking at failures until the test suite has completed running,
|
||||
|
||||
* It isn't a very accessible format for identifying patterns in test failures,
|
||||
|
||||
* It can be hard (or cumbersome) to re-run any tests that have failed.
|
||||
|
||||
Why the name ``cricket``? `Test Cricket`_ is the most prestigious version of
|
||||
the game of cricket. Games last for up to 5 days... just like running some
|
||||
test suites. The usual approach for making cricket watchable is a generous
|
||||
dose of beer; in programming, `Balmer Peak`_ limits come into effect, so
|
||||
something else is required...
|
||||
|
||||
.. _BeeWare suite: http://pybee.org/
|
||||
.. _http://pybee.org/cricket: http://pybee.org/cricket
|
||||
.. _Test Cricket: http://en.wikipedia.org/wiki/Test_cricket
|
||||
.. _Balmer Peak: http://xkcd.com/323/
|
||||
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
||||
At present, Cricket has support for:
|
||||
|
||||
* Pre-Django 1.6 project test suites,
|
||||
* Django 1.6+ project test suites using unittest2-style discovery, and
|
||||
* unittest project test suites.
|
||||
|
||||
In your Django project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-django
|
||||
|
||||
``cricket-django`` will also work in Django's own tests directory -- i.e., you
|
||||
can use ``cricket-django`` to run Django's own test suite (for Django 1.6 or
|
||||
later).
|
||||
|
||||
In a unittest project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-unittest
|
||||
|
||||
This will pop up a GUI window. Hit "Run all", and watch your test suite
|
||||
execute. A progress bar is displayed in the bottom right hand corner of
|
||||
the window, along with an estimate of time remaining.
|
||||
|
||||
While the suite is running, you can click on test names to see the output
|
||||
of that test. If the test passed, it will be displayed in green; other test
|
||||
results will be shown in other colors.
|
||||
|
||||
Problems under Ubuntu
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu's packaging of Python omits the ``idlelib`` library from it's base
|
||||
package. If you're using Python 2.7 on Ubuntu 13.04, you can install
|
||||
``idlelib`` by running::
|
||||
|
||||
$ sudo apt-get install idle-python2.7
|
||||
|
||||
For other versions of Python and Ubuntu, you'll need to adjust this as
|
||||
appropriate.
|
||||
|
||||
Problems under Windows
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you're running Cricket in a virtualenv under Windows, you'll need to set an
|
||||
environment variable so that Cricket can find the TCL graphics library::
|
||||
|
||||
$ set TCL_LIBRARY=c:\Python27\tcl\tcl8.5
|
||||
|
||||
You'll need to adjust the exact path to reflect your local Python install.
|
||||
You may find it helpful to put this line in the ``activate.bat`` script
|
||||
for your virtual environment so that it is automatically set whenever the
|
||||
virtualenv is activated.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation for cricket can be found on `Read The Docs`_.
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. You can talk to the community through:
|
||||
|
||||
* `@pybeeware on Twitter`_
|
||||
|
||||
* The `BeeWare Users Mailing list`_, for questions about how to use the BeeWare suite.
|
||||
|
||||
* The `BeeWare Developers Mailing list`_, for discussing the development of new features in the BeeWare suite, and ideas for new tools for the suite.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you experience problems with cricket, `log them on GitHub`_. If you want to contribute code, please `fork the code`_ and `submit a pull request`_.
|
||||
|
||||
.. _Read The Docs: https://cricket.readthedocs.io
|
||||
.. _@pybeeware on Twitter: https://twitter.com/pybeeware
|
||||
.. _BeeWare Users Mailing list: https://groups.google.com/forum/#!forum/beeware-users
|
||||
.. _BeeWare Developers Mailing list: https://groups.google.com/forum/#!forum/beeware-developers
|
||||
.. _log them on Github: https://github.com/pybee/cricket/issues
|
||||
.. _fork the code: https://github.com/pybee/cricket
|
||||
.. _submit a pull request: https://github.com/pybee/cricket/pulls
|
||||
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Classifier: Topic :: Utilities
|
113
cricket/README.rst
Normal file
113
cricket/README.rst
Normal file
|
@ -0,0 +1,113 @@
|
|||
Cricket
|
||||
=======
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. The project website is `http://pybee.org/cricket`_.
|
||||
|
||||
Cricket is a graphical tool that helps you run your test suites.
|
||||
|
||||
Normal unittest test runners dump all output to the console, and provide very
|
||||
little detail while the suite is running. As a result:
|
||||
|
||||
* You can't start looking at failures until the test suite has completed running,
|
||||
|
||||
* It isn't a very accessible format for identifying patterns in test failures,
|
||||
|
||||
* It can be hard (or cumbersome) to re-run any tests that have failed.
|
||||
|
||||
Why the name ``cricket``? `Test Cricket`_ is the most prestigious version of
|
||||
the game of cricket. Games last for up to 5 days... just like running some
|
||||
test suites. The usual approach for making cricket watchable is a generous
|
||||
dose of beer; in programming, `Balmer Peak`_ limits come into effect, so
|
||||
something else is required...
|
||||
|
||||
.. _BeeWare suite: http://pybee.org/
|
||||
.. _http://pybee.org/cricket: http://pybee.org/cricket
|
||||
.. _Test Cricket: http://en.wikipedia.org/wiki/Test_cricket
|
||||
.. _Balmer Peak: http://xkcd.com/323/
|
||||
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
||||
At present, Cricket has support for:
|
||||
|
||||
* Pre-Django 1.6 project test suites,
|
||||
* Django 1.6+ project test suites using unittest2-style discovery, and
|
||||
* unittest project test suites.
|
||||
|
||||
In your Django project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-django
|
||||
|
||||
``cricket-django`` will also work in Django's own tests directory -- i.e., you
|
||||
can use ``cricket-django`` to run Django's own test suite (for Django 1.6 or
|
||||
later).
|
||||
|
||||
In a unittest project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-unittest
|
||||
|
||||
This will pop up a GUI window. Hit "Run all", and watch your test suite
|
||||
execute. A progress bar is displayed in the bottom right hand corner of
|
||||
the window, along with an estimate of time remaining.
|
||||
|
||||
While the suite is running, you can click on test names to see the output
|
||||
of that test. If the test passed, it will be displayed in green; other test
|
||||
results will be shown in other colors.
|
||||
|
||||
Problems under Ubuntu
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu's packaging of Python omits the ``idlelib`` library from it's base
|
||||
package. If you're using Python 2.7 on Ubuntu 13.04, you can install
|
||||
``idlelib`` by running::
|
||||
|
||||
$ sudo apt-get install idle-python2.7
|
||||
|
||||
For other versions of Python and Ubuntu, you'll need to adjust this as
|
||||
appropriate.
|
||||
|
||||
Problems under Windows
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you're running Cricket in a virtualenv under Windows, you'll need to set an
|
||||
environment variable so that Cricket can find the TCL graphics library::
|
||||
|
||||
$ set TCL_LIBRARY=c:\Python27\tcl\tcl8.5
|
||||
|
||||
You'll need to adjust the exact path to reflect your local Python install.
|
||||
You may find it helpful to put this line in the ``activate.bat`` script
|
||||
for your virtual environment so that it is automatically set whenever the
|
||||
virtualenv is activated.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation for cricket can be found on `Read The Docs`_.
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. You can talk to the community through:
|
||||
|
||||
* `@pybeeware on Twitter`_
|
||||
|
||||
* The `BeeWare Users Mailing list`_, for questions about how to use the BeeWare suite.
|
||||
|
||||
* The `BeeWare Developers Mailing list`_, for discussing the development of new features in the BeeWare suite, and ideas for new tools for the suite.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you experience problems with cricket, `log them on GitHub`_. If you want to contribute code, please `fork the code`_ and `submit a pull request`_.
|
||||
|
||||
.. _Read The Docs: https://cricket.readthedocs.io
|
||||
.. _@pybeeware on Twitter: https://twitter.com/pybeeware
|
||||
.. _BeeWare Users Mailing list: https://groups.google.com/forum/#!forum/beeware-users
|
||||
.. _BeeWare Developers Mailing list: https://groups.google.com/forum/#!forum/beeware-developers
|
||||
.. _log them on Github: https://github.com/pybee/cricket/issues
|
||||
.. _fork the code: https://github.com/pybee/cricket
|
||||
.. _submit a pull request: https://github.com/pybee/cricket/pulls
|
||||
|
135
cricket/cricket.egg-info/PKG-INFO
Normal file
135
cricket/cricket.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,135 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: cricket
|
||||
Version: 0.2.5
|
||||
Summary: A graphical tool to assist running test suites.
|
||||
Home-page: http://pybee.org/cricket
|
||||
Author: Russell Keith-Magee
|
||||
Author-email: russell@keith-magee.com
|
||||
License: New BSD
|
||||
Description: Cricket
|
||||
=======
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. The project website is `http://pybee.org/cricket`_.
|
||||
|
||||
Cricket is a graphical tool that helps you run your test suites.
|
||||
|
||||
Normal unittest test runners dump all output to the console, and provide very
|
||||
little detail while the suite is running. As a result:
|
||||
|
||||
* You can't start looking at failures until the test suite has completed running,
|
||||
|
||||
* It isn't a very accessible format for identifying patterns in test failures,
|
||||
|
||||
* It can be hard (or cumbersome) to re-run any tests that have failed.
|
||||
|
||||
Why the name ``cricket``? `Test Cricket`_ is the most prestigious version of
|
||||
the game of cricket. Games last for up to 5 days... just like running some
|
||||
test suites. The usual approach for making cricket watchable is a generous
|
||||
dose of beer; in programming, `Balmer Peak`_ limits come into effect, so
|
||||
something else is required...
|
||||
|
||||
.. _BeeWare suite: http://pybee.org/
|
||||
.. _http://pybee.org/cricket: http://pybee.org/cricket
|
||||
.. _Test Cricket: http://en.wikipedia.org/wiki/Test_cricket
|
||||
.. _Balmer Peak: http://xkcd.com/323/
|
||||
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
||||
At present, Cricket has support for:
|
||||
|
||||
* Pre-Django 1.6 project test suites,
|
||||
* Django 1.6+ project test suites using unittest2-style discovery, and
|
||||
* unittest project test suites.
|
||||
|
||||
In your Django project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-django
|
||||
|
||||
``cricket-django`` will also work in Django's own tests directory -- i.e., you
|
||||
can use ``cricket-django`` to run Django's own test suite (for Django 1.6 or
|
||||
later).
|
||||
|
||||
In a unittest project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-unittest
|
||||
|
||||
This will pop up a GUI window. Hit "Run all", and watch your test suite
|
||||
execute. A progress bar is displayed in the bottom right hand corner of
|
||||
the window, along with an estimate of time remaining.
|
||||
|
||||
While the suite is running, you can click on test names to see the output
|
||||
of that test. If the test passed, it will be displayed in green; other test
|
||||
results will be shown in other colors.
|
||||
|
||||
Problems under Ubuntu
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu's packaging of Python omits the ``idlelib`` library from it's base
|
||||
package. If you're using Python 2.7 on Ubuntu 13.04, you can install
|
||||
``idlelib`` by running::
|
||||
|
||||
$ sudo apt-get install idle-python2.7
|
||||
|
||||
For other versions of Python and Ubuntu, you'll need to adjust this as
|
||||
appropriate.
|
||||
|
||||
Problems under Windows
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you're running Cricket in a virtualenv under Windows, you'll need to set an
|
||||
environment variable so that Cricket can find the TCL graphics library::
|
||||
|
||||
$ set TCL_LIBRARY=c:\Python27\tcl\tcl8.5
|
||||
|
||||
You'll need to adjust the exact path to reflect your local Python install.
|
||||
You may find it helpful to put this line in the ``activate.bat`` script
|
||||
for your virtual environment so that it is automatically set whenever the
|
||||
virtualenv is activated.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation for cricket can be found on `Read The Docs`_.
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. You can talk to the community through:
|
||||
|
||||
* `@pybeeware on Twitter`_
|
||||
|
||||
* The `BeeWare Users Mailing list`_, for questions about how to use the BeeWare suite.
|
||||
|
||||
* The `BeeWare Developers Mailing list`_, for discussing the development of new features in the BeeWare suite, and ideas for new tools for the suite.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you experience problems with cricket, `log them on GitHub`_. If you want to contribute code, please `fork the code`_ and `submit a pull request`_.
|
||||
|
||||
.. _Read The Docs: https://cricket.readthedocs.io
|
||||
.. _@pybeeware on Twitter: https://twitter.com/pybeeware
|
||||
.. _BeeWare Users Mailing list: https://groups.google.com/forum/#!forum/beeware-users
|
||||
.. _BeeWare Developers Mailing list: https://groups.google.com/forum/#!forum/beeware-developers
|
||||
.. _log them on Github: https://github.com/pybee/cricket/issues
|
||||
.. _fork the code: https://github.com/pybee/cricket
|
||||
.. _submit a pull request: https://github.com/pybee/cricket/pulls
|
||||
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Classifier: Topic :: Utilities
|
44
cricket/cricket.egg-info/SOURCES.txt
Normal file
44
cricket/cricket.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,44 @@
|
|||
AUTHORS
|
||||
LICENSE
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
requirements_dev.txt
|
||||
setup.cfg
|
||||
setup.py
|
||||
cricket/__init__.py
|
||||
cricket/compat.py
|
||||
cricket/events.py
|
||||
cricket/executor.py
|
||||
cricket/main.py
|
||||
cricket/model.py
|
||||
cricket/pipes.py
|
||||
cricket/view.py
|
||||
cricket.egg-info/PKG-INFO
|
||||
cricket.egg-info/SOURCES.txt
|
||||
cricket.egg-info/dependency_links.txt
|
||||
cricket.egg-info/entry_points.txt
|
||||
cricket.egg-info/pbr.json
|
||||
cricket.egg-info/requires.txt
|
||||
cricket.egg-info/top_level.txt
|
||||
cricket/django/__init__.py
|
||||
cricket/django/__main__.py
|
||||
cricket/django/discoverer.py
|
||||
cricket/django/django_runtests.py
|
||||
cricket/django/executor.py
|
||||
cricket/django/model.py
|
||||
cricket/unittest/__init__.py
|
||||
cricket/unittest/__main__.py
|
||||
cricket/unittest/discoverer.py
|
||||
cricket/unittest/executor.py
|
||||
cricket/unittest/model.py
|
||||
docs/Makefile
|
||||
docs/conf.py
|
||||
docs/index.rst
|
||||
docs/make.bat
|
||||
docs/releases.rst
|
||||
docs/internals/backends.rst
|
||||
docs/internals/contributing.rst
|
||||
docs/internals/roadmap.rst
|
||||
tests/__init__.py
|
||||
tests/test_models.py
|
||||
tests/test_unit_integration.py
|
1
cricket/cricket.egg-info/dependency_links.txt
Normal file
1
cricket/cricket.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
4
cricket/cricket.egg-info/entry_points.txt
Normal file
4
cricket/cricket.egg-info/entry_points.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
[console_scripts]
|
||||
cricket-django = cricket.django.__main__:main
|
||||
cricket-unittest = cricket.unittest.__main__:main
|
||||
|
1
cricket/cricket.egg-info/pbr.json
Normal file
1
cricket/cricket.egg-info/pbr.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"is_release": false, "git_version": "4636fe3"}
|
1
cricket/cricket.egg-info/requires.txt
Normal file
1
cricket/cricket.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
tkreadonly
|
2
cricket/cricket.egg-info/top_level.txt
Normal file
2
cricket/cricket.egg-info/top_level.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
cricket
|
||||
tests
|
9
cricket/cricket/__init__.py
Normal file
9
cricket/cricket/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Examples of valid version strings
|
||||
# __version__ = '1.2.3.dev1' # Development release 1
|
||||
# __version__ = '1.2.3a1' # Alpha Release 1
|
||||
# __version__ = '1.2.3b1' # Beta Release 1
|
||||
# __version__ = '1.2.3rc1' # RC Release 1
|
||||
# __version__ = '1.2.3' # Final Release
|
||||
# __version__ = '1.2.3.post1' # Post Release 1
|
||||
|
||||
__version__ = '0.2.5'
|
14
cricket/cricket/compat.py
Normal file
14
cricket/cricket/compat.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
# This is for backwards compatibility with Python version < 2.7 only
|
||||
# for Python 2.7 and 3.x the default unittest is the correct package
|
||||
# to use. unittest2 is a backport of this package to be used in
|
||||
# versions < 2.7.
|
||||
# Use
|
||||
# from cricket.compat import unittest
|
||||
#
|
||||
# to make versions work with all version of Python. This will be slowly
|
||||
# deprecated in the future as Python < 2.7 becomes more and mor
|
||||
# obsolete.
|
||||
from __future__ import absolute_import
|
||||
import unittest # NOQA
|
||||
if not hasattr(unittest.TestCase, 'assertIsNotNone'):
|
||||
import unittest2 as unittest # NOQA
|
0
cricket/cricket/django/__init__.py
Normal file
0
cricket/cricket/django/__init__.py
Normal file
13
cricket/cricket/django/__main__.py
Normal file
13
cricket/cricket/django/__main__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
'''
|
||||
This is the main entry point for running Django test suites.
|
||||
'''
|
||||
from cricket.main import main as cricket_main
|
||||
from cricket.django.model import DjangoProject
|
||||
|
||||
|
||||
def main():
|
||||
cricket_main(DjangoProject)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
39
cricket/cricket/django/discoverer.py
Normal file
39
cricket/cricket/django/discoverer.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
try:
|
||||
from django.test.simple import DjangoTestSuiteRunner
|
||||
except ImportError:
|
||||
DjangoTestSuiteRunner = None
|
||||
from django.test.utils import get_runner
|
||||
|
||||
# Dynamically retrieve the test runner class for this project.
|
||||
TestRunnerClass = get_runner(settings, None)
|
||||
|
||||
|
||||
class TestDiscoverer(TestRunnerClass):
|
||||
"""A Django test runner that prints out all the test that will be run.
|
||||
|
||||
Doesn't actually run any of the tests.
|
||||
"""
|
||||
def _output_suite(self, suite):
|
||||
for test in suite:
|
||||
# Django 1.6 introduce the new-style test runner.
|
||||
# If that test runner is in use, we use the full test name.
|
||||
# If we're still using a pre 1.6-style runner, we need to
|
||||
# drop out all everything between the app name and the test module.
|
||||
if isinstance(test, unittest.TestSuite):
|
||||
self._output_suite(test)
|
||||
elif (DjangoTestSuiteRunner
|
||||
and issubclass(TestRunnerClass, DjangoTestSuiteRunner)):
|
||||
parts = test.id().split('.')
|
||||
tests_index = parts.index('tests')
|
||||
print('%s.%s.%s' % (parts[tests_index - 1], parts[-2], parts[-1]))
|
||||
else:
|
||||
print(test.id())
|
||||
|
||||
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
||||
self._output_suite(self.build_suite(test_labels))
|
||||
return 0
|
49
cricket/cricket/django/django_runtests.py
Normal file
49
cricket/cricket/django/django_runtests.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import warnings
|
||||
import argparse
|
||||
|
||||
from django.utils import importlib
|
||||
|
||||
import runtests
|
||||
|
||||
|
||||
def django_tests(runner, labels):
|
||||
state = runtests.setup(1, labels)
|
||||
|
||||
module_name, runner_class_name = runner.rsplit('.', 1)
|
||||
module = importlib.import_module(module_name)
|
||||
TestRunner = getattr(module, runner_class_name)
|
||||
|
||||
runner = TestRunner(
|
||||
verbosity=1,
|
||||
interactive=False,
|
||||
failfast=False,
|
||||
)
|
||||
|
||||
# Catch warnings thrown in test DB setup -- remove in Django 1.9
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
"Custom SQL location '<app_label>/models/sql' is deprecated, "
|
||||
"use '<app_label>/sql' instead.",
|
||||
PendingDeprecationWarning
|
||||
)
|
||||
failures = runner.run_tests(labels or runtests.get_installed())
|
||||
|
||||
runtests.teardown(state)
|
||||
return failures
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("--settings", help="The settings file to use.", action="store")
|
||||
parser.add_argument("--testrunner", help="The test runner to use.", action="store")
|
||||
parser.add_argument('args', nargs=argparse.REMAINDER, help='Test labels to execute.')
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", options.settings)
|
||||
|
||||
django_tests(options.testrunner, options.args)
|
48
cricket/cricket/django/executor.py
Normal file
48
cricket/cricket/django/executor.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
from coverage import coverage
|
||||
except ImportError:
|
||||
coverage = None
|
||||
|
||||
from django.conf import settings
|
||||
try:
|
||||
from django.test.simple import DjangoTestSuiteRunner
|
||||
except ImportError:
|
||||
DjangoTestSuiteRunner = None
|
||||
from django.test.utils import get_runner
|
||||
|
||||
from cricket.pipes import PipedTestRunner
|
||||
|
||||
# Dynamically retrieve the test runner class for this project.
|
||||
TestRunnerClass = get_runner(settings, None)
|
||||
|
||||
|
||||
class TestExecutor(TestRunnerClass):
|
||||
"""A Django test runner that runs the test suite.
|
||||
|
||||
Formats output in a machine-readable format.
|
||||
"""
|
||||
def run_suite(self, suite, **kwargs):
|
||||
# Django 1.6 introduce the new-style test runner.
|
||||
# If that test runner is in use, we use the full test name.
|
||||
# If we're still using a pre 1.6-style runner, we need to
|
||||
# drop out all everything between the app name and the test module.
|
||||
use_old_discovery = (DjangoTestSuiteRunner and
|
||||
issubclass(TestRunnerClass, DjangoTestSuiteRunner))
|
||||
|
||||
return PipedTestRunner(use_old_discovery=use_old_discovery).run(suite)
|
||||
|
||||
|
||||
class TestCoverageExecutor(TestExecutor):
|
||||
"""A Django test runner that runs the test suite with coverage
|
||||
|
||||
Formats output in a machine-readable format.
|
||||
"""
|
||||
def run_suite(self, suite, **kwargs):
|
||||
cov = coverage()
|
||||
cov.start()
|
||||
result = super(TestCoverageExecutor, self).run_suite(suite, **kwargs)
|
||||
cov.stop()
|
||||
cov.save()
|
||||
return result
|
74
cricket/cricket/django/model.py
Normal file
74
cricket/cricket/django/model.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
'''
|
||||
In general, you would expect that there would only be one project class
|
||||
specified in this file. It provides the interface to executing test
|
||||
collecetion and execution.
|
||||
'''
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cricket.model import Project
|
||||
|
||||
|
||||
class DjangoProject(Project):
|
||||
'''
|
||||
The Project is a wrapper around the command-line calls to interface
|
||||
to test collection and test execution
|
||||
'''
|
||||
|
||||
def __init__(self, options=None):
|
||||
self.settings = None
|
||||
if options and hasattr(options, 'settings'):
|
||||
self.settings = options.settings
|
||||
super(DjangoProject, self).__init__()
|
||||
|
||||
@classmethod
|
||||
def add_arguments(cls, parser):
|
||||
"""Add Django-specific settings to the argument parser.
|
||||
"""
|
||||
settings_help = ("The Python path to a settings module, e.g. "
|
||||
"\"myproject.settings.main\". If this isn't provided, the "
|
||||
"DJANGO_SETTINGS_MODULE environment variable will be "
|
||||
"used.")
|
||||
parser.add_argument('--settings', help=settings_help)
|
||||
|
||||
@property
|
||||
def script(self):
|
||||
if os.path.exists(os.path.join(os.getcwd(), 'manage.py')):
|
||||
# We're running the test suite on a normal Django project
|
||||
script = ['manage.py', 'test', '--noinput']
|
||||
elif os.path.exists(os.path.join(os.getcwd(), 'runtests.py')):
|
||||
# We're running Django's own test script
|
||||
script = [os.path.join(os.path.dirname(__file__), 'django_runtests.py')]
|
||||
os.environ['PYTHONPATH'] = os.getcwd()
|
||||
if self.settings is None:
|
||||
self.settings = 'test_sqlite'
|
||||
else:
|
||||
raise Exception("Can't find a Django test suite to execute.")
|
||||
return script
|
||||
|
||||
def discover_commandline(self):
|
||||
"Command lineDiscover all available tests in a project."
|
||||
|
||||
command = [sys.executable] + self.script
|
||||
|
||||
if self.settings:
|
||||
command.append('--settings={0}'.format(self.settings))
|
||||
|
||||
command.append('--testrunner=cricket.django.discoverer.TestDiscoverer')
|
||||
|
||||
return command
|
||||
|
||||
def execute_commandline(self, labels):
|
||||
"Return the command line to execute the specified test labels"
|
||||
command = [sys.executable] + self.script
|
||||
|
||||
if self.settings:
|
||||
command.append('--settings={0}'.format(self.settings))
|
||||
|
||||
if self.coverage:
|
||||
command.append('--testrunner=cricket.django.executor.TestCoverageExecutor')
|
||||
else:
|
||||
command.append('--testrunner=cricket.django.executor.TestExecutor')
|
||||
command.extend(labels)
|
||||
|
||||
return command
|
21
cricket/cricket/events.py
Normal file
21
cricket/cricket/events.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
|
||||
class EventSource(object):
|
||||
"""A source of GUI events.
|
||||
|
||||
An event source can receive handlers for events, and
|
||||
can emit events.
|
||||
"""
|
||||
_events = {}
|
||||
|
||||
@classmethod
|
||||
def bind(cls, event, handler):
|
||||
cls._events.setdefault(cls, {}).setdefault(event, []).append(handler)
|
||||
|
||||
def emit(self, event, **data):
|
||||
try:
|
||||
for handler in self._events[self.__class__][event]:
|
||||
handler(self, **data)
|
||||
except KeyError:
|
||||
# No handler registered for event.
|
||||
pass
|
260
cricket/cricket/executor.py
Normal file
260
cricket/cricket/executor.py
Normal file
|
@ -0,0 +1,260 @@
|
|||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from threading import Thread
|
||||
|
||||
try:
|
||||
from Queue import Queue, Empty
|
||||
except ImportError:
|
||||
from queue import Queue, Empty # python 3.x
|
||||
|
||||
from cricket.events import EventSource
|
||||
from cricket.model import TestMethod
|
||||
from cricket.pipes import PipedTestResult, PipedTestRunner
|
||||
|
||||
|
||||
def enqueue_output(out, queue):
|
||||
"""A utility method for consuming piped output from a subprocess.
|
||||
|
||||
Reads content from `out` one line at a time, and puts it onto
|
||||
queue for consumption in a separate thread.
|
||||
"""
|
||||
for line in iter(out.readline, b''):
|
||||
queue.put(line.strip().decode('utf-8'))
|
||||
out.close()
|
||||
|
||||
|
||||
class Executor(EventSource):
|
||||
"A wrapper around the subprocess that executes tests."
|
||||
def __init__(self, project, module, count, labels):
|
||||
self.project = project
|
||||
self.module = module
|
||||
|
||||
self.proc = subprocess.Popen(
|
||||
self.project.execute_commandline(labels),
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
close_fds='posix' in sys.builtin_module_names
|
||||
)
|
||||
|
||||
# Piped stdout/stderr reads are blocking; therefore, we need to
|
||||
# do all our readline calls in a background thread, and use a
|
||||
# queue object to store lines that have been read.
|
||||
self.stdout = Queue()
|
||||
t = Thread(target=enqueue_output, args=(self.proc.stdout, self.stdout))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
self.stderr = Queue()
|
||||
t = Thread(target=enqueue_output, args=(self.proc.stderr, self.stderr))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
# The TestMethod object currently under execution.
|
||||
self.current_test = None
|
||||
|
||||
# An accumulator of ouput from the tests. If buffer is None,
|
||||
# then the test suite isn't currently running - it's in suite
|
||||
# setup/teardown.
|
||||
self.buffer = None
|
||||
|
||||
# An accumulator for error output from the tests.
|
||||
self.error_buffer = []
|
||||
|
||||
# The timestamp when current_test started
|
||||
self.start_time = None
|
||||
|
||||
# The total count of tests under execution
|
||||
self.total_count = count
|
||||
|
||||
# The count of tests that have been executed.
|
||||
self.completed_count = 0
|
||||
|
||||
# The count of specific test results.
|
||||
self.result_count = {}
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
"Return True if this runner currently running."
|
||||
return self.proc.poll() is None
|
||||
|
||||
@property
|
||||
def any_failed(self):
|
||||
return sum(self.result_count.get(state, 0) for state in TestMethod.FAILING_STATES)
|
||||
|
||||
def terminate(self):
|
||||
"Stop the executor."
|
||||
self.proc.terminate()
|
||||
|
||||
def poll(self):
|
||||
"Poll the runner looking for new test output"
|
||||
stopped = False
|
||||
finished = False
|
||||
|
||||
# Read from stdout, building a buffer.
|
||||
lines = []
|
||||
try:
|
||||
while True:
|
||||
lines.append(self.stdout.get(block=False))
|
||||
except Empty:
|
||||
# queue.get() raises an exception when the queue is empty.
|
||||
# This means there is no more output to consume at this time.
|
||||
pass
|
||||
|
||||
# Read from stderr, building a buffer.
|
||||
try:
|
||||
while True:
|
||||
self.error_buffer.append(self.stderr.get(block=False))
|
||||
except Empty:
|
||||
# queue.get() raises an exception when the queue is empty.
|
||||
# This means there is no more output to consume at this time.
|
||||
pass
|
||||
|
||||
# Check to see if the subprocess is still running.
|
||||
# If it isn't, raise an error.
|
||||
if self.proc is None:
|
||||
stopped = True
|
||||
elif self.proc.poll() is not None:
|
||||
stopped = True
|
||||
|
||||
# Process all the full lines that are available
|
||||
for line in lines:
|
||||
# Look for a separator.
|
||||
if line in (PipedTestResult.RESULT_SEPARATOR, PipedTestRunner.START_TEST_RESULTS, PipedTestRunner.END_TEST_RESULTS):
|
||||
if self.buffer is None:
|
||||
# Preamble is finished. Set up the line buffer.
|
||||
self.buffer = []
|
||||
else:
|
||||
# Start of new test result; record the last result
|
||||
# Then, work out what content goes where.
|
||||
pre = json.loads(self.buffer[0])
|
||||
post = json.loads(self.buffer[1])
|
||||
|
||||
if post['status'] == 'OK':
|
||||
status = TestMethod.STATUS_PASS
|
||||
error = None
|
||||
elif post['status'] == 's':
|
||||
status = TestMethod.STATUS_SKIP
|
||||
error = 'Skipped: ' + post.get('error')
|
||||
elif post['status'] == 'F':
|
||||
status = TestMethod.STATUS_FAIL
|
||||
error = post.get('error')
|
||||
elif post['status'] == 'x':
|
||||
status = TestMethod.STATUS_EXPECTED_FAIL
|
||||
error = post.get('error')
|
||||
elif post['status'] == 'u':
|
||||
status = TestMethod.STATUS_UNEXPECTED_SUCCESS
|
||||
error = None
|
||||
elif post['status'] == 'E':
|
||||
status = TestMethod.STATUS_ERROR
|
||||
error = post.get('error')
|
||||
|
||||
output = post.get('output')
|
||||
if output:
|
||||
print(f'{self.current_test.path}:')
|
||||
print(output)
|
||||
|
||||
if error:
|
||||
print(error)
|
||||
|
||||
# Increase the count of executed tests
|
||||
self.completed_count = self.completed_count + 1
|
||||
|
||||
# Get the start and end times for the test
|
||||
start_time = float(pre['start_time'])
|
||||
end_time = float(post['end_time'])
|
||||
|
||||
self.current_test.description = post['description']
|
||||
|
||||
self.current_test.set_result(
|
||||
status=status,
|
||||
output=output,
|
||||
error=error,
|
||||
duration=end_time - start_time,
|
||||
)
|
||||
|
||||
# Work out how long the suite has left to run (approximately)
|
||||
if self.start_time is None:
|
||||
self.start_time = start_time
|
||||
total_duration = end_time - self.start_time
|
||||
time_per_test = total_duration / self.completed_count
|
||||
remaining_time = (self.total_count - self.completed_count) * time_per_test
|
||||
if remaining_time > 4800:
|
||||
remaining = '%s hours' % int(remaining_time / 2400)
|
||||
elif remaining_time > 2400:
|
||||
remaining = '%s hour' % int(remaining_time / 2400)
|
||||
elif remaining_time > 120:
|
||||
remaining = '%s mins' % int(remaining_time / 60)
|
||||
elif remaining_time > 60:
|
||||
remaining = '%s min' % int(remaining_time / 60)
|
||||
else:
|
||||
remaining = '%ss' % int(remaining_time)
|
||||
|
||||
# Update test result counts
|
||||
self.result_count.setdefault(status, 0)
|
||||
self.result_count[status] = self.result_count[status] + 1
|
||||
|
||||
# Notify the display to update.
|
||||
self.emit('test_end', module=self.module, test_path=self.current_test.path, result=status, remaining_time=remaining)
|
||||
|
||||
# Clear the decks for the next test.
|
||||
self.current_test = None
|
||||
self.buffer = []
|
||||
|
||||
if line == PipedTestRunner.END_TEST_RESULTS:
|
||||
# End of test execution.
|
||||
# Mark the runner as finished, and move back
|
||||
# to a pre-test state in the results.
|
||||
finished = True
|
||||
self.buffer = None
|
||||
|
||||
else:
|
||||
# Not a separator line, so it's actual content.
|
||||
if self.buffer is None:
|
||||
# Suite isn't running yet - just display the output
|
||||
# as a status update line.
|
||||
self.emit('test_status_update', module=self.module, update=line)
|
||||
else:
|
||||
# Suite is running - have we got an active test?
|
||||
# Doctest (and some other tools) output invisible escape sequences.
|
||||
# Strip these if they exist.
|
||||
if line.startswith('\x1b'):
|
||||
line = line[line.find('{'):]
|
||||
|
||||
# Store the cleaned buffer
|
||||
self.buffer.append(line)
|
||||
|
||||
# If we don't have an currently active test, this line will
|
||||
# contain the path for the test.
|
||||
if self.current_test is None:
|
||||
try:
|
||||
# No active test; first line tells us which test is running.
|
||||
pre = json.loads(line)
|
||||
except ValueError:
|
||||
self.emit('suit_end', module=self.module)
|
||||
return True
|
||||
self.current_test = self.project.confirm_exists(pre['path'])
|
||||
self.emit('test_start', module=self.module, test_path=pre['path'])
|
||||
# If we're not finished, requeue the event.
|
||||
if finished:
|
||||
if self.error_buffer:
|
||||
self.emit('suite_end', module=self.module, error='\n'.join(self.error_buffer))
|
||||
else:
|
||||
self.emit('suite_end', module=self.module)
|
||||
return False
|
||||
|
||||
elif stopped:
|
||||
# Suite has stopped producing output.
|
||||
if self.error_buffer:
|
||||
self.emit('suite_error', module=self.module, error='\n'.join(self.error_buffer))
|
||||
else:
|
||||
self.emit('suite_error', module=self.module, error='Test output ended unexpectedly')
|
||||
|
||||
# Suite has finished; don't requeue
|
||||
return False
|
||||
|
||||
else:
|
||||
# Still running - requeue event.
|
||||
return True
|
21
cricket/cricket/lang.py
Normal file
21
cricket/cricket/lang.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import Union
|
||||
import os
|
||||
import json
|
||||
|
||||
class SimpleLang(object):
|
||||
def __init__(self, file = None):
|
||||
file = file if file else 'languages.json'
|
||||
self._load_from_file(file)
|
||||
self._current_lang = 'zh'
|
||||
|
||||
def _load_from_file(self, file: str):
|
||||
path = os.path.dirname(os.path.abspath(__file__)) + '/' + file
|
||||
with open(path) as f:
|
||||
self.lang_dict = json.load(f)
|
||||
|
||||
@property
|
||||
def current_lang(self):
|
||||
return self._current_lang
|
||||
|
||||
def get_text(self, key):
|
||||
return self.lang_dict[self._current_lang][key]
|
36
cricket/cricket/languages.json
Normal file
36
cricket/cricket/languages.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"zh": {
|
||||
"title": "工厂测试",
|
||||
"run_all_button": "运行",
|
||||
"run_selected_button": "运行选择",
|
||||
"stop_button": "停止",
|
||||
"quit_button": "退出",
|
||||
"reboot_button": "重启",
|
||||
"poweroff_button": "关闭",
|
||||
"test_table_head": ["模块", "测试项", "结果"],
|
||||
"test_step": "测试步骤",
|
||||
"start_button": "开始",
|
||||
"play_button": "播放",
|
||||
"record_button": "录音",
|
||||
"playback_button": "回放",
|
||||
"pass_button": "通过",
|
||||
"fail_button": "失败"
|
||||
},
|
||||
"en": {
|
||||
"title": "Factory Test",
|
||||
"run_all_button": "Run",
|
||||
"run_selected_button": "Run Selected",
|
||||
"stop_button": "Stop",
|
||||
"quit_button": "Quit",
|
||||
"reboot_button": "Reboot",
|
||||
"poweroff_button": "Shutdown",
|
||||
"test_table_head": ["Module", "Test Item", "Result"],
|
||||
"test_step": "Test Step",
|
||||
"start_button": "Start",
|
||||
"play_button": "Play",
|
||||
"record_button": "Record",
|
||||
"playback_button": "Playback",
|
||||
"pass_button": "Pass",
|
||||
"fail_button": "Fail"
|
||||
}
|
||||
}
|
112
cricket/cricket/main.py
Normal file
112
cricket/cricket/main.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
'''
|
||||
The purpose of this module is to set up the Cricket GUI,
|
||||
load a "project" for discovering and executing tests, and
|
||||
to initiate the GUI main loop.
|
||||
'''
|
||||
from argparse import ArgumentParser
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# try:
|
||||
# from Tkinter import *
|
||||
# except ImportError:
|
||||
# from tkinter import *
|
||||
|
||||
# from cricket.view import (
|
||||
# MainWindow,
|
||||
# TestLoadErrorDialog,
|
||||
# IgnorableTestLoadErrorDialog
|
||||
# )
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from cricket.qtview import MainWindow
|
||||
from cricket.model import ModelLoadError
|
||||
|
||||
|
||||
def main(Model):
|
||||
"""Run the main loop of the app.
|
||||
|
||||
Take the project Model as the argument. This model will be
|
||||
instantiated as part of the main loop.
|
||||
"""
|
||||
parser = ArgumentParser()
|
||||
|
||||
parser.add_argument("--version", help="Display version number and exit", action="store_true")
|
||||
|
||||
Model.add_arguments(parser)
|
||||
options = parser.parse_args()
|
||||
|
||||
# Check the shortcut options
|
||||
if options.version:
|
||||
import cricket
|
||||
print(cricket.__version__)
|
||||
return
|
||||
|
||||
# Set up the root Tk context
|
||||
# root = Tk()
|
||||
|
||||
# Construct an empty window
|
||||
# view = MainWindow(root)
|
||||
|
||||
# Try to load the project. If any error occurs during
|
||||
# project load, show an error dialog
|
||||
# project = None
|
||||
# while project is None:
|
||||
try:
|
||||
# Create the project objects
|
||||
project = Model(options)
|
||||
|
||||
runner = subprocess.Popen(
|
||||
project.discover_commandline(),
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
)
|
||||
|
||||
test_list = []
|
||||
for line in runner.stdout:
|
||||
test = line.strip().decode('utf-8')
|
||||
if test.startswith('unittest.loader._FailedTest.'):
|
||||
print('Load failed:', test.replace('unittest.loader._FailedTest.', ''))
|
||||
sys.exit(1)
|
||||
test_list.append(line.strip().decode('utf-8'))
|
||||
|
||||
errors = []
|
||||
for line in runner.stderr:
|
||||
errors.append(line.strip().decode('utf-8'))
|
||||
if errors and not test_list:
|
||||
raise ModelLoadError('\n'.join(errors))
|
||||
|
||||
project.refresh(test_list, errors)
|
||||
except ModelLoadError as e:
|
||||
# Load failed; destroy the project and show an error dialog.
|
||||
# If the user selects cancel, quit.
|
||||
project = None
|
||||
sys.exit(1)
|
||||
# dialog = TestLoadErrorDialog(root, e.trace)
|
||||
# if dialog.status == dialog.CANCEL:
|
||||
# sys.exit(1)
|
||||
if project.errors:
|
||||
sys.exit(1)
|
||||
# dialog = IgnorableTestLoadErrorDialog(root, '\n'.join(project.errors))
|
||||
# if dialog.status == dialog.CANCEL:
|
||||
# sys.exit(1)
|
||||
|
||||
# Set up the root Tk context
|
||||
app = QApplication([])
|
||||
|
||||
# Construct an empty window
|
||||
view = MainWindow(app)
|
||||
|
||||
# Set the project for the main window.
|
||||
# This populates the tree, and sets listeners for
|
||||
# future tree modifications.
|
||||
view.project = project
|
||||
|
||||
# Run the main loop
|
||||
try:
|
||||
view.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
view.on_quit()
|
514
cricket/cricket/model.py
Normal file
514
cricket/cricket/model.py
Normal file
|
@ -0,0 +1,514 @@
|
|||
"""A module containing a data representation for the test suite.
|
||||
|
||||
This is the "Model" of the MVC world.
|
||||
|
||||
Each object in the model is an event source; views/controllers
|
||||
can bind to events on the model to be notified of changes.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from cricket.events import EventSource
|
||||
|
||||
|
||||
class ModelLoadError(Exception):
|
||||
def __init__(self, trace):
|
||||
super(ModelLoadError, self).__init__()
|
||||
self.trace = trace
|
||||
|
||||
|
||||
class TestMethod(EventSource):
|
||||
"""A data representation of an individual test method.
|
||||
|
||||
Emits:
|
||||
* 'new' when a new node is added
|
||||
* 'inactive' when the test method is made inactive in the suite.
|
||||
* 'active' when the test method is made active in the suite.
|
||||
* 'status_update' when the pass/fail status of the method is updated.
|
||||
"""
|
||||
STATUS_PASS = 100
|
||||
STATUS_SKIP = 200
|
||||
STATUS_FAIL = 300
|
||||
STATUS_EXPECTED_FAIL = 310
|
||||
STATUS_UNEXPECTED_SUCCESS = 320
|
||||
STATUS_ERROR = 400
|
||||
|
||||
FAILING_STATES = (STATUS_FAIL, STATUS_UNEXPECTED_SUCCESS, STATUS_ERROR)
|
||||
|
||||
STATUS_LABELS = {
|
||||
STATUS_PASS: 'passed',
|
||||
STATUS_SKIP: 'skipped',
|
||||
STATUS_FAIL: 'failures',
|
||||
STATUS_EXPECTED_FAIL: 'expected failures',
|
||||
STATUS_UNEXPECTED_SUCCESS: 'unexpected successes',
|
||||
STATUS_ERROR: 'errors',
|
||||
}
|
||||
|
||||
def __init__(self, name, testCase):
|
||||
self.name = name
|
||||
self.description = ''
|
||||
self._active = True
|
||||
self._result = None
|
||||
|
||||
# Set the parent of the TestMethod
|
||||
self.parent = testCase
|
||||
self.parent[name] = self
|
||||
self.parent._update_active()
|
||||
|
||||
# Announce that there is a new test method
|
||||
self.emit('new')
|
||||
|
||||
def __repr__(self):
|
||||
return u'TestMethod %s' % self.path
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"The dotted-path name that identifies this test method to the test runner"
|
||||
return u'%s.%s' % (self.parent.path, self.name)
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"Is this test method currently active?"
|
||||
return self._active
|
||||
|
||||
def set_active(self, is_active, cascade=True):
|
||||
"""Explicitly set the active state of the test method
|
||||
|
||||
If cascade is True, the parent testCase will be prompted
|
||||
to check it's current active status.
|
||||
"""
|
||||
if self._active:
|
||||
if not is_active:
|
||||
self._active = False
|
||||
self.emit('inactive')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
else:
|
||||
if is_active:
|
||||
self._active = True
|
||||
self.emit('active')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
|
||||
def toggle_active(self):
|
||||
"Toggle the current active status of this test method"
|
||||
self.set_active(not self.active)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
try:
|
||||
return self._result['status']
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
try:
|
||||
return self._result['output']
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def error(self):
|
||||
try:
|
||||
return self._result['error']
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
try:
|
||||
return self._result['duration']
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
def set_result(self, status, output, error, duration):
|
||||
self._result = {
|
||||
'status': status,
|
||||
'output': output,
|
||||
'error': error,
|
||||
'duration': duration,
|
||||
}
|
||||
self.emit('status_update')
|
||||
|
||||
|
||||
class TestCase(dict, EventSource):
|
||||
"""A data representation of a test case, wrapping multiple test methods.
|
||||
|
||||
Emits:
|
||||
* 'new' when a new node is added
|
||||
* 'inactive' when the test method is made inactive in the suite.
|
||||
* 'active' when the test method is made active in the suite.
|
||||
"""
|
||||
def __init__(self, name, testApp):
|
||||
super(TestCase, self).__init__()
|
||||
self.name = name
|
||||
self._active = True
|
||||
|
||||
# Set the parent of the TestCase
|
||||
self.parent = testApp
|
||||
self.parent[name] = self
|
||||
self.parent._update_active()
|
||||
|
||||
# Announce that there is a new TestCase
|
||||
self.emit('new')
|
||||
|
||||
def __repr__(self):
|
||||
return u'TestCase %s' % self.path
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"The dotted-path name that identifies this Test Case to the test runner"
|
||||
return u'%s.%s' % (self.parent.path, self.name)
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"Is this test method currently active?"
|
||||
return self._active
|
||||
|
||||
def set_active(self, is_active, cascade=True):
|
||||
"""Explicitly set the active state of the test case.
|
||||
|
||||
Forces all methods on this test case to set to the same
|
||||
active status.
|
||||
|
||||
If cascade is True, the parent test module will be prompted
|
||||
to check it's current active status.
|
||||
"""
|
||||
if self._active:
|
||||
if not is_active:
|
||||
self._active = False
|
||||
self.emit('inactive')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
for testMethod in self.values():
|
||||
testMethod.set_active(False, cascade=False)
|
||||
else:
|
||||
if is_active:
|
||||
self._active = True
|
||||
self.emit('active')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
for testMethod in self.values():
|
||||
testMethod.set_active(True, cascade=False)
|
||||
|
||||
def toggle_active(self):
|
||||
"Toggle the current active status of this test case"
|
||||
self.set_active(not self.active)
|
||||
|
||||
def find_tests(self, active=True, status=None, labels=None):
|
||||
"""Find the test labels matching the search criteria.
|
||||
|
||||
This will check:
|
||||
* active: if the method is currently an active test
|
||||
* status: if the last run status of the method is in the provided list
|
||||
* labels: if the method label is in the provided list
|
||||
|
||||
Returns a count of tests found, plus the labels needed to
|
||||
execute those tests.
|
||||
"""
|
||||
tests = []
|
||||
count = 0
|
||||
|
||||
for testMethod_name, testMethod in self.items():
|
||||
include = True
|
||||
# If only active tests have been requested, the method
|
||||
# must be active.
|
||||
if active and not testMethod.active:
|
||||
include = False
|
||||
|
||||
# If a list of statuses has been provided, the
|
||||
# method status must be in that list.
|
||||
if status and testMethod.status not in status:
|
||||
include = False
|
||||
|
||||
# If a list of test labels has been provided, the method
|
||||
# must be named explicitly
|
||||
if labels and testMethod.path not in labels:
|
||||
include = False
|
||||
|
||||
if include:
|
||||
count = count + 1
|
||||
tests.append(testMethod.path)
|
||||
|
||||
# If all the tests are included, then just reference the test case.
|
||||
if len(self) == count:
|
||||
return len(self), self.path
|
||||
|
||||
return count, tests
|
||||
|
||||
def _purge(self, timestamp):
|
||||
"Purge any test method that isn't current as of the timestamp"
|
||||
for testMethod_name, testMethod in self.items():
|
||||
if testMethod.timestamp != timestamp:
|
||||
self.pop(testMethod_name)
|
||||
|
||||
def _update_active(self):
|
||||
"Check the active status of all child nodes, and update the status of this node accordingly"
|
||||
for testMethod_name, testMethod in self.items():
|
||||
if testMethod.active:
|
||||
# As soon as we find an active child, this node
|
||||
# must be marked active, and no other checks are
|
||||
# required.
|
||||
self.set_active(True)
|
||||
return
|
||||
self.set_active(False)
|
||||
|
||||
|
||||
class TestModule(dict, EventSource):
|
||||
"""A data representation of a module. It may contain test cases, or other modules.
|
||||
|
||||
Emits:
|
||||
* 'new' when a new node is added
|
||||
* 'inactive' when the test method is made inactive in the suite.
|
||||
* 'active' when the test method is made active in the suite.
|
||||
"""
|
||||
def __init__(self, name, parent):
|
||||
super(TestModule, self).__init__()
|
||||
self.name = name
|
||||
self._active = True
|
||||
|
||||
# Set the parent of the TestModule.
|
||||
self.parent = parent
|
||||
self.parent[name] = self
|
||||
|
||||
# Announce that there is a new test case
|
||||
self.emit('new')
|
||||
|
||||
def __repr__(self):
|
||||
return u'TestModule %s' % self.path
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"The dotted-path name that identifies this app to the test runner"
|
||||
if self.parent.path:
|
||||
return u'%s.%s' % (self.parent.path, self.name)
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"Is this test method currently active?"
|
||||
return self._active
|
||||
|
||||
def set_active(self, is_active, cascade=True):
|
||||
"""Explicitly set the active state of the test case.
|
||||
|
||||
Forces all test cases and test modules held by this test module
|
||||
to be set to the same active status
|
||||
|
||||
If cascade is True, the parent test module will be prompted
|
||||
to check it's current active status.
|
||||
"""
|
||||
if self._active:
|
||||
if not is_active:
|
||||
self._active = False
|
||||
self.emit('inactive')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
for testModule in self.values():
|
||||
testModule.set_active(False, cascade=False)
|
||||
else:
|
||||
if is_active:
|
||||
self._active = True
|
||||
self.emit('active')
|
||||
if cascade:
|
||||
self.parent._update_active()
|
||||
for testModule in self.values():
|
||||
testModule.set_active(True, cascade=False)
|
||||
|
||||
def toggle_active(self):
|
||||
"Toggle the current active status of this test case"
|
||||
self.set_active(not self.active)
|
||||
|
||||
def find_tests(self, active=True, status=None, labels=None):
|
||||
"""Find the test labels matching the search criteria.
|
||||
|
||||
This will check:
|
||||
* active: if the method is currently an active test
|
||||
* status: if the last run status of the method is in the provided list
|
||||
* labels: if the method label is in the provided list
|
||||
|
||||
Returns a count of tests found, plus the labels needed to
|
||||
execute those tests.
|
||||
"""
|
||||
tests = []
|
||||
count = 0
|
||||
|
||||
found_partial = False
|
||||
for testModule_name, testModule in self.items():
|
||||
include = True
|
||||
|
||||
# If only active tests have been requested, the module
|
||||
# must be active.
|
||||
if active and not testModule.active:
|
||||
include = False
|
||||
|
||||
# If a list of test labels has been provided, either the
|
||||
# module, or a test *in* the module, must be named explicitly.
|
||||
if labels:
|
||||
if testModule.path in labels:
|
||||
# The module is named explicitly. Include all active
|
||||
# subtests of this module
|
||||
subcount, subtests = testModule.find_tests(True, status)
|
||||
else:
|
||||
# The module isn't named. Look for all subtests.
|
||||
# Search for subtests that match.
|
||||
subcount, subtests = testModule.find_tests(active, status, labels)
|
||||
else:
|
||||
subcount, subtests = testModule.find_tests(active, status)
|
||||
|
||||
if include:
|
||||
count = count + subcount
|
||||
|
||||
if isinstance(subtests, list):
|
||||
found_partial = True
|
||||
tests.extend(subtests)
|
||||
else:
|
||||
tests.append(subtests)
|
||||
|
||||
# No partials found; just reference the app.
|
||||
if not found_partial:
|
||||
return count, self.path
|
||||
|
||||
return count, tests
|
||||
|
||||
def _purge(self, timestamp):
|
||||
"""Search all submodules and test cases looking for stale test methods.
|
||||
|
||||
Purge any test module without any test cases, and any test Case with no
|
||||
test methods.
|
||||
"""
|
||||
for testModule_name, testModule in self.items():
|
||||
testModule._purge(timestamp)
|
||||
if len(testModule) == 0:
|
||||
self.pop(testModule_name)
|
||||
|
||||
def _update_active(self):
|
||||
"Check the active status of all child nodes, and update the status of this node accordingly"
|
||||
for subModule_name, subModule in self.items():
|
||||
if subModule.active:
|
||||
self.set_active(True)
|
||||
return
|
||||
self.set_active(False)
|
||||
|
||||
|
||||
class Project(dict, EventSource):
|
||||
"""A data representation of an project, containing 1+ test apps.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(Project, self).__init__()
|
||||
self.errors = []
|
||||
self.coverage = False
|
||||
|
||||
def __repr__(self):
|
||||
return u'Project'
|
||||
|
||||
@classmethod
|
||||
def add_arguments(cls, parser):
|
||||
"""Add project specific commandline arguments to the *parser*
|
||||
object. *parser* is an instance of argparse.ArgumentParser.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"The dotted-path name that identifies this project to the test runner"
|
||||
return ''
|
||||
|
||||
def find_tests(self, active=True, status=None, labels=None):
|
||||
"""Find the test labels matching the search criteria.
|
||||
|
||||
Returns a count of tests found, plus the labels needed to
|
||||
execute those tests.
|
||||
"""
|
||||
tests = []
|
||||
count = 0
|
||||
|
||||
found_partial = False
|
||||
for testApp_name, testApp in self.items():
|
||||
include = True
|
||||
|
||||
# If only active tests have been requested, the module
|
||||
# must be active.
|
||||
if active and not testApp.active:
|
||||
include = False
|
||||
|
||||
# If a list of test labels has been provided, either the
|
||||
# module, or a test *in* the module, must be named explicitly.
|
||||
if labels:
|
||||
if testApp.path in labels:
|
||||
# The module is named explicitly. Include all active
|
||||
# subtests of this module
|
||||
subcount, subtests = testApp.find_tests(True, status)
|
||||
else:
|
||||
# The module isn't named. Look for all subtests.
|
||||
# Search for subtests that match.
|
||||
subcount, subtests = testApp.find_tests(active, status, labels)
|
||||
else:
|
||||
subcount, subtests = testApp.find_tests(active, status)
|
||||
|
||||
if include:
|
||||
count = count + subcount
|
||||
|
||||
if isinstance(subtests, list):
|
||||
found_partial = True
|
||||
tests.extend(subtests)
|
||||
else:
|
||||
tests.append(subtests)
|
||||
|
||||
# No partials found; just reference the app.
|
||||
if not found_partial:
|
||||
return count, []
|
||||
|
||||
return count, tests
|
||||
|
||||
def confirm_exists(self, test_label, timestamp=None):
|
||||
"""Confirm that the given test label exists in the current data model.
|
||||
|
||||
If it doesn't, create a representation for it.
|
||||
"""
|
||||
parts = test_label.split('.')
|
||||
if len(parts) < 2:
|
||||
return
|
||||
|
||||
parentModule = self
|
||||
for testModule_name in parts[:-2]:
|
||||
try:
|
||||
testModule = parentModule[testModule_name]
|
||||
except KeyError:
|
||||
testModule = TestModule(testModule_name, parentModule)
|
||||
parentModule = testModule
|
||||
|
||||
try:
|
||||
testCase = parentModule[parts[-2]]
|
||||
except KeyError:
|
||||
testCase = TestCase(parts[-2], parentModule)
|
||||
|
||||
try:
|
||||
testMethod = testCase[parts[-1]]
|
||||
except KeyError:
|
||||
testMethod = TestMethod(parts[-1], testCase)
|
||||
|
||||
testMethod.timestamp = timestamp
|
||||
return testMethod
|
||||
|
||||
def refresh(self, test_list, errors=None):
|
||||
"""Refresh the project representation so that it contains only the tests in test_list
|
||||
|
||||
test_list should be a list of dotted-path test names.
|
||||
"""
|
||||
timestamp = datetime.now()
|
||||
|
||||
# Make sure there is a data representation for every test in the list.
|
||||
for test_label in test_list:
|
||||
self.confirm_exists(test_label, timestamp)
|
||||
|
||||
for testModule_name, testModule in self.items():
|
||||
testModule._purge(timestamp)
|
||||
if len(testModule) == 0:
|
||||
self.pop(testModule_name)
|
||||
|
||||
self.errors = errors if errors is not None else []
|
||||
|
||||
def _update_active(self):
|
||||
"Exists for API consistency"
|
||||
pass
|
220
cricket/cricket/pipes.py
Normal file
220
cricket/cricket/pipes.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
import unittest2 as unittest
|
||||
else:
|
||||
import unittest
|
||||
|
||||
|
||||
def trim_docstring(docstring):
|
||||
"""Trim leading spaces in docstring indentation.
|
||||
|
||||
Algorithm taken from PEP 257:
|
||||
http://www.python.org/dev/peps/pep-0257/#id20
|
||||
"""
|
||||
# Convert tabs to spaces (following the normal Python rules)
|
||||
# and split into a list of lines:
|
||||
lines = docstring.expandtabs().splitlines()
|
||||
# Determine minimum indentation (first line doesn't count):
|
||||
indent = sys.maxsize
|
||||
for line in lines[1:]:
|
||||
stripped = line.lstrip()
|
||||
if stripped:
|
||||
indent = min(indent, len(line) - len(stripped))
|
||||
# Remove indentation (first line is special):
|
||||
trimmed = [lines[0].strip()]
|
||||
if indent < sys.maxsize:
|
||||
for line in lines[1:]:
|
||||
trimmed.append(line[indent:].rstrip())
|
||||
# Strip off trailing and leading blank lines:
|
||||
while trimmed and not trimmed[-1]:
|
||||
trimmed.pop()
|
||||
while trimmed and not trimmed[0]:
|
||||
trimmed.pop(0)
|
||||
# Return a single string:
|
||||
return '\n'.join(trimmed)
|
||||
|
||||
|
||||
class PipedTestResult(unittest.result.TestResult):
|
||||
"""A test result class that can print test results in a machine-parseable format.
|
||||
|
||||
Used by PipedTestRunner.
|
||||
"""
|
||||
RESULT_SEPARATOR = '\x1f' # ASCII US (Unit Separator)
|
||||
|
||||
def __init__(self, stream, use_old_discovery=True):
|
||||
super(PipedTestResult, self).__init__()
|
||||
self.stream = stream
|
||||
self.use_old_discovery = use_old_discovery
|
||||
self._first = True
|
||||
|
||||
# Create a clean buffer for stdout content.
|
||||
self._stdout = StringIO()
|
||||
sys.stdout = self._stdout
|
||||
|
||||
# The test runner is very lightly stateful. It's possible
|
||||
# for a test to raise an error before the test has actually
|
||||
# started; we need to make sure that we output a header line
|
||||
# for the misbehaving test.
|
||||
self._current_test = None
|
||||
|
||||
def description(self, test):
|
||||
try:
|
||||
# Wrapped _ErrorHolder objects have their own description
|
||||
return trim_docstring(test.description)
|
||||
except AttributeError:
|
||||
# Fall back to the docstring on the method itself.
|
||||
if test._testMethodDoc:
|
||||
return trim_docstring(test._testMethodDoc)
|
||||
else:
|
||||
return 'No description'
|
||||
|
||||
def startTest(self, test):
|
||||
super(PipedTestResult, self).startTest(test)
|
||||
# We know we're starting a new test - record it.
|
||||
self._current_test = test
|
||||
self._stdout = StringIO()
|
||||
sys.stdout = self._stdout
|
||||
|
||||
if self.use_old_discovery:
|
||||
parts = test.id().split('.')
|
||||
tests_index = parts.index('tests')
|
||||
path = '%s.%s.%s' % (parts[tests_index - 1], parts[-2], parts[-1])
|
||||
else:
|
||||
path = test.id()
|
||||
|
||||
body = {
|
||||
'path': path,
|
||||
'start_time': time.time()
|
||||
}
|
||||
if self._first:
|
||||
self.stream.write(PipedTestRunner.START_TEST_RESULTS + '\n')
|
||||
self._first = False
|
||||
else:
|
||||
self.stream.write(self.RESULT_SEPARATOR + '\n')
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(PipedTestResult, self).addSuccess(test)
|
||||
body = {
|
||||
'status': 'OK',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
def addError(self, test, err):
|
||||
# If there's no current test, the error occurred during test
|
||||
# setup. Output a test start line so the protocol isn't confused.
|
||||
if self._current_test is None:
|
||||
self.startTest(test)
|
||||
|
||||
super(PipedTestResult, self).addError(test, err)
|
||||
body = {
|
||||
'status': 'E',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'error': ''.join(traceback.format_exception(*err)),
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
def addFailure(self, test, err):
|
||||
super(PipedTestResult, self).addFailure(test, err)
|
||||
body = {
|
||||
'status': 'F',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'error': ''.join(traceback.format_exception(*err)),
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
super(PipedTestResult, self).addSkip(test, reason)
|
||||
body = {
|
||||
'status': 's',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'error': reason,
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
super(PipedTestResult, self).addExpectedFailure(test, err)
|
||||
body = {
|
||||
'status': 'x',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'error': ''.join(traceback.format_exception(*err)),
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
super(PipedTestResult, self).addUnexpectedSuccess(test)
|
||||
body = {
|
||||
'status': 'u',
|
||||
'end_time': time.time(),
|
||||
'description': self.description(test),
|
||||
'output': self._stdout.getvalue(),
|
||||
}
|
||||
self.stream.write('%s\n' % json.dumps(body))
|
||||
self.stream.flush()
|
||||
self._current_test = None
|
||||
|
||||
|
||||
class PipedTestRunner(unittest.TextTestRunner):
|
||||
"""A test runner class that displays results in machine-parseable format.
|
||||
|
||||
It prints out the names of tests as they are run, errors as they
|
||||
occur, and a summary of the results at the end of the test run.
|
||||
"""
|
||||
START_TEST_RESULTS = '\x02' # ASCII STX (Start of Text)
|
||||
END_TEST_RESULTS = '\x03' # ASCII ETX (End of Text)
|
||||
|
||||
def __init__(self, stream=sys.stdout, use_old_discovery=False):
|
||||
self.stream = stream
|
||||
self.use_old_discovery = use_old_discovery
|
||||
|
||||
def run(self, test):
|
||||
"Run the given test case or test suite."
|
||||
# Remember stdout reference so it can be restored later
|
||||
old_stdout = sys.stdout
|
||||
|
||||
# Create the result pipe, and run the tests with it.
|
||||
result = PipedTestResult(self.stream, self.use_old_discovery)
|
||||
# test is TestSuite, as TestSuite.run(result)
|
||||
# via BaseTestSuite.__call__
|
||||
test(result)
|
||||
|
||||
# Report end of test run
|
||||
self.stream.write(self.END_TEST_RESULTS + '\n')
|
||||
self.stream.flush()
|
||||
|
||||
# Restore the stdout reference
|
||||
sys.stdout = old_stdout
|
||||
|
||||
return result
|
509
cricket/cricket/qtview.py
Normal file
509
cricket/cricket/qtview.py
Normal file
|
@ -0,0 +1,509 @@
|
|||
"""A module containing a visual representation of the testModule.
|
||||
|
||||
This is the "View" of the MVC world.
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtWidgets import (
|
||||
QMainWindow,
|
||||
QFrame,
|
||||
QVBoxLayout,
|
||||
QGridLayout,
|
||||
QPushButton,
|
||||
QGroupBox,
|
||||
QTableWidget,
|
||||
QStatusBar,
|
||||
QTableWidgetItem
|
||||
)
|
||||
|
||||
import os
|
||||
from importlib import import_module
|
||||
|
||||
from cricket.model import TestMethod, TestCase, TestModule
|
||||
from cricket.executor import Executor
|
||||
from cricket.lang import SimpleLang
|
||||
|
||||
|
||||
# Display constants for test status
|
||||
STATUS = {
|
||||
TestMethod.STATUS_PASS: {
|
||||
'description': u'Pass',
|
||||
'symbol': u'\u25cf',
|
||||
'tag': 'pass',
|
||||
'color': '#28C025',
|
||||
},
|
||||
TestMethod.STATUS_SKIP: {
|
||||
'description': u'Skipped',
|
||||
'symbol': u'S',
|
||||
'tag': 'skip',
|
||||
'color': '#259EBF'
|
||||
},
|
||||
TestMethod.STATUS_FAIL: {
|
||||
'description': u'Failure',
|
||||
'symbol': u'F',
|
||||
'tag': 'fail',
|
||||
'color': '#E32C2E'
|
||||
},
|
||||
TestMethod.STATUS_EXPECTED_FAIL: {
|
||||
'description': u'Expected\n failure',
|
||||
'symbol': u'X',
|
||||
'tag': 'expected',
|
||||
'color': '#3C25BF'
|
||||
},
|
||||
TestMethod.STATUS_UNEXPECTED_SUCCESS: {
|
||||
'description': u'Unexpected\n success',
|
||||
'symbol': u'U',
|
||||
'tag': 'unexpected',
|
||||
'color': '#C82788'
|
||||
},
|
||||
TestMethod.STATUS_ERROR: {
|
||||
'description': 'Error',
|
||||
'symbol': u'E',
|
||||
'tag': 'error',
|
||||
'color': '#E4742C'
|
||||
},
|
||||
}
|
||||
|
||||
STATUS_DEFAULT = {
|
||||
'description': 'Not\nexecuted',
|
||||
'symbol': u'',
|
||||
'tag': None,
|
||||
'color': '#BFBFBF',
|
||||
}
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, SimpleLang):
|
||||
def __init__(self, root):
|
||||
super().__init__()
|
||||
|
||||
self._project = None
|
||||
self.test_table = {}
|
||||
self.test_list = {}
|
||||
self.run_status = {}
|
||||
self.executor = {}
|
||||
|
||||
self.root = root
|
||||
self.setWindowTitle(self.get_text('title'))
|
||||
# self.showFullScreen()
|
||||
|
||||
# Set up the main content for the window.
|
||||
self._setup_main_content()
|
||||
|
||||
# Set up listeners for runner events.
|
||||
Executor.bind('test_status_update', self.on_executorStatusUpdate)
|
||||
Executor.bind('test_start', self.on_executorTestStart)
|
||||
Executor.bind('test_end', self.on_executorTestEnd)
|
||||
Executor.bind('suite_end', self.on_executorSuiteEnd)
|
||||
Executor.bind('suite_error', self.on_executorSuiteError)
|
||||
|
||||
######################################################
|
||||
# Internal GUI layout methods.
|
||||
######################################################
|
||||
|
||||
def _setup_main_content(self):
|
||||
'''
|
||||
The button toolbar runs as a horizontal area at the top of the GUI.
|
||||
It is a persistent GUI component
|
||||
'''
|
||||
|
||||
self.content = QFrame(self)
|
||||
self.content_layout = QVBoxLayout(self.content)
|
||||
|
||||
toolbar = QFrame(self.content)
|
||||
layout = QGridLayout(toolbar)
|
||||
|
||||
self.run_all_button = QPushButton(self.get_text('run_all_button'), toolbar)
|
||||
self.run_all_button.clicked.connect(self.cmd_run_all)
|
||||
self.run_all_button.setFocus()
|
||||
layout.addWidget(self.run_all_button, 0, 0)
|
||||
|
||||
self.run_selected_button = QPushButton(self.get_text('run_selected_button'),
|
||||
toolbar)
|
||||
self.run_selected_button.setDisabled(True)
|
||||
self.run_selected_button.clicked.connect(self.cmd_run_selected)
|
||||
layout.addWidget(self.run_selected_button, 0, 1)
|
||||
|
||||
self.stop_button = QPushButton(self.get_text('stop_button'), toolbar)
|
||||
self.stop_button.setDisabled(True)
|
||||
self.stop_button.clicked.connect(self.cmd_stop)
|
||||
layout.addWidget(self.stop_button, 0 , 2)
|
||||
|
||||
self.reboot_button = QPushButton(self.get_text('reboot_button'), toolbar)
|
||||
self.reboot_button.clicked.connect(self.cmd_reboot)
|
||||
layout.addWidget(self.reboot_button, 0, 3)
|
||||
|
||||
self.poweroff_button = QPushButton(self.get_text('poweroff_button'), toolbar)
|
||||
self.poweroff_button.clicked.connect(self.cmd_poweroff)
|
||||
layout.addWidget(self.poweroff_button, 0, 4)
|
||||
|
||||
self.content_layout.addWidget(toolbar)
|
||||
|
||||
self.setCentralWidget(self.content)
|
||||
|
||||
def _setup_test_table(self, name):
|
||||
module = import_module(name)
|
||||
|
||||
box = QGroupBox(module.MODULE_NAME[self.current_lang], self.content)
|
||||
layout = QVBoxLayout(box)
|
||||
|
||||
columns = self.get_text('test_table_head')
|
||||
|
||||
table = QTableWidget(box)
|
||||
table.setColumnCount(len(columns))
|
||||
table.setHorizontalHeaderLabels(columns)
|
||||
table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
table.itemSelectionChanged.connect(self.on_testMethodSelected)
|
||||
layout.addWidget(table)
|
||||
self.test_table[name] = table
|
||||
|
||||
status = QStatusBar(box)
|
||||
status.showMessage('Not running')
|
||||
layout.addWidget(status)
|
||||
self.run_status[name] = status
|
||||
|
||||
self.content_layout.addWidget(box)
|
||||
|
||||
######################################################
|
||||
# Handlers for setting a new project
|
||||
######################################################
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
return self._project
|
||||
|
||||
def _get_text(self, subModuleName, subModule, key):
|
||||
module = import_module(subModule.parent.path)
|
||||
testcase = getattr(module, subModuleName)
|
||||
if hasattr(testcase, 'LANGUAGES'):
|
||||
langs = getattr(testcase, 'LANGUAGES')
|
||||
lang = langs.get(self.current_lang)
|
||||
if lang is not None:
|
||||
text = lang.get(key)
|
||||
if text is not None:
|
||||
return text
|
||||
|
||||
return key
|
||||
|
||||
def _add_test_module(self, parentNode, testModule):
|
||||
for subModuleName, subModule in sorted(testModule.items()):
|
||||
if isinstance(subModule, TestModule):
|
||||
self._add_test_module(parentNode, subModule)
|
||||
else:
|
||||
for testMethod_name, testMethod in sorted(subModule.items()):
|
||||
self.test_list.setdefault(parentNode, []).append(testMethod.path)
|
||||
|
||||
table = self.test_table[parentNode]
|
||||
row = table.rowCount()
|
||||
table.insertRow(row)
|
||||
item = QTableWidgetItem(self._get_text(subModuleName, subModule, subModuleName))
|
||||
table.setItem(row, 0, item)
|
||||
item = QTableWidgetItem(self._get_text(subModuleName, subModule, testMethod_name))
|
||||
table.setItem(row, 1, item)
|
||||
item = QTableWidgetItem('')
|
||||
item.setData(Qt.UserRole, testMethod.path)
|
||||
table.setItem(row, 2, item)
|
||||
|
||||
@project.setter
|
||||
def project(self, project):
|
||||
self._project = project
|
||||
|
||||
# Get a count of active tests to display in the status bar.
|
||||
# count, labels = self.project.find_tests(True)
|
||||
# self.run_summary.set('T:%s P:0 F:0 E:0 X:0 U:0 S:0' % count)
|
||||
|
||||
# Populate the initial tree nodes. This is recursive, because
|
||||
# the tree could be of arbitrary depth.
|
||||
for testModule_name, testModule in sorted(project.items()):
|
||||
self._setup_test_table(testModule_name)
|
||||
self._add_test_module(testModule_name, testModule)
|
||||
self.executor[testModule_name] = None
|
||||
|
||||
self.showFullScreen()
|
||||
|
||||
TestMethod.bind('status_update', self.on_nodeStatusUpdate)
|
||||
|
||||
######################################################
|
||||
# TK Main loop
|
||||
######################################################
|
||||
|
||||
def mainloop(self):
|
||||
self.root.exec_()
|
||||
|
||||
######################################################
|
||||
# User commands
|
||||
######################################################
|
||||
|
||||
def cmd_poweroff(self):
|
||||
self.stop()
|
||||
# self.root.quit()
|
||||
os.system('poweroff')
|
||||
|
||||
def cmd_reboot(self):
|
||||
self.stop()
|
||||
# self.root.quit()
|
||||
os.system('reboot')
|
||||
|
||||
def cmd_quit(self):
|
||||
"Command: Quit"
|
||||
# If the runner is currently running, kill it.
|
||||
self.stop()
|
||||
|
||||
self.root.quit()
|
||||
|
||||
def cmd_stop(self, event=None):
|
||||
"Command: The stop button has been pressed"
|
||||
self.stop()
|
||||
|
||||
def cmd_run_all(self, event=None):
|
||||
"Command: The Run all button has been pressed"
|
||||
# If the executor isn't currently running, we can
|
||||
# start a test run.
|
||||
for module, executor in self.executor.items():
|
||||
if not executor or not executor.is_running:
|
||||
self.run(module)
|
||||
|
||||
def cmd_run_selected(self, event=None):
|
||||
"Command: The 'run selected' button has been pressed"
|
||||
for module, table in self.test_table.items():
|
||||
# If the executor isn't currently running, we can
|
||||
# start a test run.
|
||||
labels = []
|
||||
for item in table.selectedItems():
|
||||
data = item.data(Qt.UserRole)
|
||||
if data is not None:
|
||||
labels.append(data)
|
||||
|
||||
if labels and (not self.executor[module] or
|
||||
not self.executor[module].is_running):
|
||||
self.run(module, labels=labels)
|
||||
|
||||
######################################################
|
||||
# GUI Callbacks
|
||||
######################################################
|
||||
|
||||
def on_testMethodSelected(self):
|
||||
"Event handler: a test case has been selected in the tree"
|
||||
# update "run selected" button enabled state
|
||||
self.set_selected_button_state()
|
||||
|
||||
def on_nodeStatusUpdate(self, node):
|
||||
"Event handler: a node on the tree has received a status update"
|
||||
module = node.path.split('.')[0]
|
||||
table = self.test_table[module]
|
||||
columnCount = table.columnCount()
|
||||
for row in range(table.rowCount()):
|
||||
item = table.item(row, columnCount-1)
|
||||
if item is not None:
|
||||
if item.data(Qt.UserRole) == node.path:
|
||||
for column in range(columnCount):
|
||||
_item = table.item(row, column)
|
||||
_item.setForeground(QColor(STATUS[node.status]['color']))
|
||||
item.setText(STATUS[node.status]['description'])
|
||||
break
|
||||
|
||||
def on_testProgress(self, executor):
|
||||
"Event handler: a periodic update to poll the runner for output, generating GUI updates"
|
||||
if executor and executor.poll():
|
||||
QTimer.singleShot(100, lambda: self.on_testProgress(executor))
|
||||
|
||||
def on_executorStatusUpdate(self, event, module, update):
|
||||
"The executor has some progress to report"
|
||||
# Update the status line.
|
||||
self.run_status[module].showMessage(update)
|
||||
|
||||
def on_executorTestStart(self, event, module, test_path):
|
||||
"The executor has started running a new test."
|
||||
# Update status line
|
||||
self.run_status[module].showMessage('Running %s...' % test_path)
|
||||
|
||||
def on_executorTestEnd(self, event, module, test_path, result, remaining_time):
|
||||
"The executor has finished running a test."
|
||||
self.run_status[module].showMessage('')
|
||||
# Update the progress meter
|
||||
# self.progress_value.set(self.progress_value.get() + 1)
|
||||
|
||||
# Update the run summary
|
||||
# self.run_summary.set('T:%(total)s P:%(pass)s F:%(fail)s E:%(error)s X:%(expected)s U:%(unexpected)s S:%(skip)s, ~%(remaining)s remaining' % {
|
||||
# 'total': self.executor.total_count,
|
||||
# 'pass': self.executor.result_count.get(TestMethod.STATUS_PASS, 0),
|
||||
# 'fail': self.executor.result_count.get(TestMethod.STATUS_FAIL, 0),
|
||||
# 'error': self.executor.result_count.get(TestMethod.STATUS_ERROR, 0),
|
||||
# 'expected': self.executor.result_count.get(TestMethod.STATUS_EXPECTED_FAIL, 0),
|
||||
# 'unexpected': self.executor.result_count.get(TestMethod.STATUS_UNEXPECTED_SUCCESS, 0),
|
||||
# 'skip': self.executor.result_count.get(TestMethod.STATUS_SKIP, 0),
|
||||
# 'remaining': remaining_time
|
||||
# })
|
||||
|
||||
# If the test that just fininshed is the one (and only one)
|
||||
# selected on the tree, update the display.
|
||||
# current_tree = self.current_test_tree
|
||||
# if len(current_tree.selection()) == 1:
|
||||
# # One test selected.
|
||||
# if current_tree.selection()[0] == test_path:
|
||||
# # If the test that just finished running is the selected
|
||||
# # test, force reset the selection, which will generate a
|
||||
# # selection event, forcing a refresh of the result page.
|
||||
# current_tree.selection_set(current_tree.selection())
|
||||
# else:
|
||||
# # No or Multiple tests selected
|
||||
# self.name.set('')
|
||||
# self.test_status.set('')
|
||||
|
||||
# self.duration.set('')
|
||||
# self.description.delete('1.0', END)
|
||||
|
||||
# self._hide_test_output()
|
||||
# self._hide_test_errors()
|
||||
|
||||
def on_executorSuiteEnd(self, event, module, error=None):
|
||||
"The test suite finished running."
|
||||
# Display the final results
|
||||
self.run_status[module].showMessage('Finished.')
|
||||
|
||||
# if error:
|
||||
# TestErrorsDialog(self.root, error)
|
||||
|
||||
# if self.executor[module].any_failed:
|
||||
# dialog = tkMessageBox.showerror
|
||||
# else:
|
||||
# dialog = tkMessageBox.showinfo
|
||||
|
||||
# message = ', '.join(
|
||||
# '%d %s' % (count, TestMethod.STATUS_LABELS[state])
|
||||
# for state, count in sorted(self.executor[module].result_count.items()))
|
||||
|
||||
# dialog(message=message or 'No tests were ran')
|
||||
|
||||
# Reset the running summary.
|
||||
# self.run_summary.set('T:%(total)s P:%(pass)s F:%(fail)s E:%(error)s X:%(expected)s U:%(unexpected)s S:%(skip)s' % {
|
||||
# 'total': self.executor.total_count,
|
||||
# 'pass': self.executor.result_count.get(TestMethod.STATUS_PASS, 0),
|
||||
# 'fail': self.executor.result_count.get(TestMethod.STATUS_FAIL, 0),
|
||||
# 'error': self.executor.result_count.get(TestMethod.STATUS_ERROR, 0),
|
||||
# 'expected': self.executor.result_count.get(TestMethod.STATUS_EXPECTED_FAIL, 0),
|
||||
# 'unexpected': self.executor.result_count.get(TestMethod.STATUS_UNEXPECTED_SUCCESS, 0),
|
||||
# 'skip': self.executor.result_count.get(TestMethod.STATUS_SKIP, 0),
|
||||
# })
|
||||
|
||||
# Reset the buttons
|
||||
self.reset_button_states_on_end()
|
||||
|
||||
# Drop the reference to the executor
|
||||
self.executor[module] = None
|
||||
|
||||
def on_executorSuiteError(self, event, module, error):
|
||||
"An error occurred running the test suite."
|
||||
# Display the error in a dialog
|
||||
self.run_status[module].showMessage('Error running test suite.')
|
||||
# FailedTestDialog(self.root, error)
|
||||
|
||||
# Drop the reference to the executor
|
||||
self.executor[module] = None
|
||||
|
||||
def reset_button_states_on_end(self):
|
||||
"A test run has ended and we should enable or disable buttons as appropriate."
|
||||
is_stoped = True
|
||||
for executor in self.executor.values():
|
||||
if executor and executor.is_running:
|
||||
is_stoped = False
|
||||
|
||||
if is_stoped:
|
||||
self.stop_button.setDisabled(True)
|
||||
self.run_all_button.setDisabled(False)
|
||||
|
||||
self.set_selected_button_state()
|
||||
|
||||
def set_selected_button_state(self):
|
||||
is_running = False
|
||||
for executor in self.executor.values():
|
||||
if executor and executor.is_running:
|
||||
is_running = True
|
||||
|
||||
is_selected = False
|
||||
for table in self.test_table.values():
|
||||
if table.selectedItems():
|
||||
is_selected = True
|
||||
|
||||
if is_running:
|
||||
self.run_selected_button.setDisabled(True)
|
||||
elif is_selected:
|
||||
self.run_selected_button.setDisabled(False)
|
||||
else:
|
||||
self.run_selected_button.setDisabled(True)
|
||||
|
||||
######################################################
|
||||
# GUI utility methods
|
||||
######################################################
|
||||
|
||||
def run(self, module, active=True, status=None, labels=None):
|
||||
"""Run the test suite.
|
||||
|
||||
If active=True, only active tests will be run.
|
||||
If status is provided, only tests whose most recent run
|
||||
status matches the set provided will be executed.
|
||||
If labels is provided, only tests with those labels will
|
||||
be executed
|
||||
"""
|
||||
labels = labels if labels else self.test_list[module]
|
||||
|
||||
self.run_status[module].showMessage('Running...')
|
||||
# self.run_summary.set('T:%s P:0 F:0 E:0 X:0 U:0 S:0' % count)
|
||||
|
||||
self.run_all_button.setDisabled(True)
|
||||
self.run_selected_button.setDisabled(True)
|
||||
self.stop_button.setDisabled(False)
|
||||
|
||||
# self.progress['maximum'] = count
|
||||
# self.progress_value.set(0)
|
||||
|
||||
# Create the runner
|
||||
self.executor[module] = Executor(self.project, module, len(labels), labels)
|
||||
|
||||
# Queue the first progress handling event
|
||||
QTimer.singleShot(100, lambda: self.on_testProgress(self.executor[module]))
|
||||
|
||||
def stop(self):
|
||||
"Stop the test suite."
|
||||
for module, executor in self.executor.items():
|
||||
if executor and executor.is_running:
|
||||
self.run_status[module].showMessage('Stopping...')
|
||||
|
||||
executor.terminate()
|
||||
executor = None
|
||||
|
||||
self.run_status[module].showMessage('Stopped.')
|
||||
|
||||
self.reset_button_states_on_end()
|
||||
|
||||
def _hide_test_output(self):
|
||||
"Hide the test output panel on the test results page"
|
||||
self.output_label.grid_remove()
|
||||
self.output.grid_remove()
|
||||
self.output_scrollbar.grid_remove()
|
||||
self.details_frame.rowconfigure(3, weight=0)
|
||||
|
||||
def _show_test_output(self, content):
|
||||
"Show the test output panel on the test results page"
|
||||
self.output.delete('1.0', END)
|
||||
self.output.insert('1.0', content)
|
||||
|
||||
self.output_label.grid()
|
||||
self.output.grid()
|
||||
self.output_scrollbar.grid()
|
||||
self.details_frame.rowconfigure(3, weight=5)
|
||||
|
||||
def _hide_test_errors(self):
|
||||
"Hide the test error panel on the test results page"
|
||||
self.error_label.grid_remove()
|
||||
self.error.grid_remove()
|
||||
self.error_scrollbar.grid_remove()
|
||||
|
||||
def _show_test_errors(self, content):
|
||||
"Show the test error panel on the test results page"
|
||||
self.error.delete('1.0', END)
|
||||
self.error.insert('1.0', content)
|
||||
|
||||
self.error_label.grid()
|
||||
self.error.grid()
|
||||
self.error_scrollbar.grid()
|
0
cricket/cricket/unittest/__init__.py
Normal file
0
cricket/cricket/unittest/__init__.py
Normal file
13
cricket/cricket/unittest/__main__.py
Normal file
13
cricket/cricket/unittest/__main__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
'''
|
||||
This is the main entry point for running unittest test suites.
|
||||
'''
|
||||
from cricket.main import main as cricket_main
|
||||
from cricket.unittest.model import UnittestProject
|
||||
|
||||
|
||||
def main():
|
||||
cricket_main(UnittestProject)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
60
cricket/cricket/unittest/discoverer.py
Normal file
60
cricket/cricket/unittest/discoverer.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
'''
|
||||
The whole purpose of this module is to generate printed output.
|
||||
It should be of the form:
|
||||
|
||||
'module.testcase.specifictest'
|
||||
'module.testcase.specifictest2'
|
||||
'module2.testcase.specifictest'
|
||||
|
||||
etc
|
||||
|
||||
Its primary API is the command-line, but it can
|
||||
just as easily be called programmatically (see __main__)
|
||||
'''
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
def consume(iterable):
|
||||
input = list(iterable)
|
||||
while input:
|
||||
item = input.pop(0)
|
||||
try:
|
||||
data = iter(item)
|
||||
input = list(data) + input
|
||||
except:
|
||||
yield item
|
||||
|
||||
|
||||
class PyTestDiscoverer:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.collected_tests = []
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
Builds the dotted namespace expected by cricket
|
||||
'''
|
||||
|
||||
resultstr = '\n'.join(self.collected_tests)
|
||||
|
||||
return resultstr.strip()
|
||||
|
||||
def collect_tests(self):
|
||||
'''
|
||||
Collect a list of potentially runnable tests
|
||||
'''
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
suite = loader.discover('.')
|
||||
flatresults = list(consume(suite))
|
||||
named = [r.id() for r in flatresults]
|
||||
self.collected_tests = named
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
PTD = PyTestDiscoverer()
|
||||
PTD.collect_tests()
|
||||
print(str(PTD))
|
90
cricket/cricket/unittest/executor.py
Normal file
90
cricket/cricket/unittest/executor.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
'''
|
||||
This is a thing which, when run, produces a stream
|
||||
of well-formed test result outputs. Its processing is
|
||||
initiated by the top-level Executor class.
|
||||
|
||||
Its main API is the command line, but it's just as sensible to
|
||||
call into it. See __main__ for usage
|
||||
'''
|
||||
import argparse
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from coverage import coverage
|
||||
except ImportError:
|
||||
coverage = None
|
||||
|
||||
from cricket import pipes
|
||||
|
||||
|
||||
class PyTestExecutor(object):
|
||||
'''
|
||||
This is a thing which, when run, produces a stream
|
||||
of well-formed test result outputs. Its processing is
|
||||
initiated by the top-level Executor class
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Allows the executor to run a specified list of tests
|
||||
self.specified_list = None
|
||||
|
||||
def run_only(self, specified_list):
|
||||
self.specified_list = specified_list
|
||||
|
||||
def stream_suite(self, suite):
|
||||
|
||||
pipes.PipedTestRunner().run(suite)
|
||||
|
||||
def stream_results(self):
|
||||
'''
|
||||
1.) Discover all tests if necessary
|
||||
2.) Otherwise fetch specific tests
|
||||
3.) Execute-and-stream
|
||||
'''
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
if not self.specified_list:
|
||||
suite = loader.discover('.')
|
||||
else:
|
||||
# FIXME: TestCase stuck when sleep in test method
|
||||
# for module in self.specified_list:
|
||||
# suite = loader.loadTestsFromName(module)
|
||||
# self.stream_suite(suite)
|
||||
suite = loader.loadTestsFromNames(self.specified_list)
|
||||
|
||||
self.stream_suite(suite)
|
||||
|
||||
|
||||
class PyTestCoverageExecutor(PyTestExecutor):
|
||||
'''
|
||||
A version of PyTestExecutor that gathers coverage data.
|
||||
'''
|
||||
def stream_suite(self, suite):
|
||||
cov = coverage()
|
||||
cov.start()
|
||||
super(PyTestCoverageExecutor, self).stream_suite(suite)
|
||||
cov.stop()
|
||||
cov.save()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("--coverage", help="Generate coverage data for the test run", action="store_true")
|
||||
parser.add_argument(
|
||||
'labels', nargs=argparse.REMAINDER,
|
||||
help='Test labels to run.'
|
||||
)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.coverage:
|
||||
PTE = PyTestCoverageExecutor()
|
||||
else:
|
||||
PTE = PyTestExecutor()
|
||||
|
||||
if options.labels:
|
||||
PTE.run_only(options.labels)
|
||||
PTE.stream_results()
|
20
cricket/cricket/unittest/model.py
Normal file
20
cricket/cricket/unittest/model.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import sys
|
||||
|
||||
from cricket.model import Project
|
||||
|
||||
|
||||
class UnittestProject(Project):
|
||||
|
||||
def __init__(self, options=None):
|
||||
super(UnittestProject, self).__init__()
|
||||
|
||||
def discover_commandline(self):
|
||||
"Command line: Discover all available tests in a project."
|
||||
return [sys.executable, '-m', 'cricket.unittest.discoverer']
|
||||
|
||||
def execute_commandline(self, labels):
|
||||
"Return the command line to execute the specified test labels"
|
||||
args = [sys.executable, '-m', 'cricket.unittest.executor']
|
||||
if self.coverage:
|
||||
args.append('--coverage')
|
||||
return args + labels
|
1173
cricket/cricket/view.py
Normal file
1173
cricket/cricket/view.py
Normal file
File diff suppressed because it is too large
Load diff
153
cricket/docs/Makefile
Normal file
153
cricket/docs/Makefile
Normal file
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
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 " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@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 " 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 " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
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."
|
||||
|
||||
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/Django-Cricket.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Django-Cricket.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Django-Cricket"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Django-Cricket"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
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)."
|
||||
|
||||
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."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
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)."
|
||||
|
||||
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."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
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."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
246
cricket/docs/conf.py
Normal file
246
cricket/docs/conf.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cricket documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Feb 9 10:44:39 2013.
|
||||
#
|
||||
# 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, os
|
||||
|
||||
sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
import cricket
|
||||
|
||||
# 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 = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
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 = u'Cricket'
|
||||
copyright = u'2013, Russell Keith-Magee'
|
||||
|
||||
# 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 = '.'.join(str(n) for n in cricket.NUM_VERSION[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = cricket.VERSION
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#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.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# 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 = []
|
||||
|
||||
|
||||
# -- 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 = 'default'
|
||||
|
||||
# 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. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# 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 (within the static path) to use as 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']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# 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
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Cricketdoc'
|
||||
|
||||
|
||||
# -- 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': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Cricket.tex', u'Cricket Documentation',
|
||||
u'Russell Keith-Magee', '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 = [
|
||||
('index', 'cricket', u'Cricket Documentation',
|
||||
[u'Russell Keith-Magee'], 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 = [
|
||||
('index', 'Cricket', u'Cricket Documentation',
|
||||
u'Russell Keith-Magee', 'Cricket', 'A graphical tool to assist running test suites.',
|
||||
'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'
|
95
cricket/docs/index.rst
Normal file
95
cricket/docs/index.rst
Normal file
|
@ -0,0 +1,95 @@
|
|||
Cricket
|
||||
=======
|
||||
|
||||
Cricket is part of the `BeeWare suite`_. The project website is `http://pybee.org/cricket`_.
|
||||
|
||||
Cricket is a graphical tool that helps projects with large test suites **identify failures without waiting** for all your tests to finish.
|
||||
|
||||
Normal unittest test runners dump all output to the console, and provide very
|
||||
little detail while the suite is running. As a result:
|
||||
|
||||
* You can't start looking at failures until the test suite has completed running,
|
||||
|
||||
* It isn't a very accessible format for identifying patterns in test failures,
|
||||
|
||||
* It can be hard (or cumbersome) to re-run any tests that have failed.
|
||||
|
||||
Why the name ``cricket``? `Test Cricket`_ is the most prestigious version of
|
||||
the game of cricket. Games last for up to 5 days... just like running some
|
||||
test suites. The usual approach for making cricket watchable is a generous
|
||||
dose of beer; in programming, `Balmer Peak`_ limits come into effect, so
|
||||
something else is required...
|
||||
|
||||
.. _BeeWare suite: http://pybee.org/
|
||||
.. _http://pybee.org/cricket: http://pybee.org/cricket
|
||||
.. _Test Cricket: http://en.wikipedia.org/wiki/Test_cricket
|
||||
.. _Balmer Peak: http://xkcd.com/323/
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
||||
At present, Cricket has support for:
|
||||
|
||||
* Pre-Django 1.6 project test suites,
|
||||
* Django 1.6+ project test suites using unittest2-style discovery, and
|
||||
* unittest project test suites.
|
||||
|
||||
In your Django project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-django
|
||||
|
||||
``cricket-django`` will also work in Django's own tests directory -- i.e., you
|
||||
can use ``cricket-django`` to run Django's own test suite (for Django 1.6 or
|
||||
later).
|
||||
|
||||
In a unittest project, install cricket, and then run it::
|
||||
|
||||
$ pip install cricket
|
||||
$ cricket-unittest
|
||||
|
||||
This will pop up a GUI window. Hit "Run all", and watch your test suite
|
||||
execute.
|
||||
|
||||
Problems under Ubuntu
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu's packaging of Python omits the ``idlelib`` library from it's base
|
||||
package. If you're using Python 2.7 on Ubuntu 13.04, you can install
|
||||
``idlelib`` by running::
|
||||
|
||||
$ sudo apt-get install idle-python2.7
|
||||
|
||||
For other versions of Python and Ubuntu, you'll need to adjust this as
|
||||
appropriate.
|
||||
|
||||
Problems under Windows
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you're running Cricket in a virtualenv, you'll need to set an
|
||||
environment variable so that Cricket can find the TCL graphics library::
|
||||
|
||||
$ set TCL_LIBRARY=c:\Python27\tcl\tcl8.5
|
||||
|
||||
You'll need to adjust the exact path to reflect your local Python install.
|
||||
You may find it helpful to put this line in the ``activate.bat`` script
|
||||
for your virtual environment so that it is automatically set whenever the
|
||||
virtualenv is activated.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:glob:
|
||||
|
||||
internals/contributing
|
||||
internals/backends
|
||||
internals/roadmap
|
||||
releases
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
59
cricket/docs/internals/backends.rst
Normal file
59
cricket/docs/internals/backends.rst
Normal file
|
@ -0,0 +1,59 @@
|
|||
Writing a Cricket backend
|
||||
=========================
|
||||
|
||||
Why you might want to do this
|
||||
-----------------------------
|
||||
|
||||
A number of test execution environments are not necessarily supported. This
|
||||
includes pytest, GUI test tools, or even custom stuff. The sky is the limit.
|
||||
Or, you might just want to understand the architecture.
|
||||
|
||||
Helicopter Overview of the Architecture
|
||||
---------------------------------------
|
||||
|
||||
The main directory consists of events, executor, model, pipes, view and
|
||||
widgets. The ones which are the concern of the GUI are events, view and
|
||||
widgets. The ones which concern the backend are model, executor and pipes.
|
||||
The one which you need to really understand is pipes, but that's not the best
|
||||
starting point.
|
||||
|
||||
The best starting point is either the unittest or django subdirectory. The GUI
|
||||
is first built by the relevant backend, and the backend provides standard
|
||||
callbacks for the GUI.
|
||||
|
||||
Layout of a Backend System
|
||||
--------------------------
|
||||
|
||||
A Cricket backend should contain the following 4 files:
|
||||
|
||||
* ``__main__.py`` - The entry point for the user.
|
||||
|
||||
* ``discoverer.py`` - Generates the list of available tests
|
||||
|
||||
* ``executor.py`` - Wraps execution of test functions
|
||||
|
||||
* ``model.py`` - Defines the method for executing the discoverer and executor
|
||||
|
||||
Requirements of a backend
|
||||
-------------------------
|
||||
|
||||
Both the Django and the unittest backend take advantage of the unittest module
|
||||
to create and execute test suites. The core file pipes.PipedTestRunner will
|
||||
run unittest-style tests and provide the appropriately well-formed output
|
||||
expected by the GUI. However, it is a valid choice for the executor to produce
|
||||
output of the same for onto stdout itself. The only hard requirement is that
|
||||
the executor function stream onto stdout a series of well-formed outputs. To
|
||||
understand the full detail, examine pipes.py.
|
||||
|
||||
The Django and the unittest mechanisms for executing tests are different. The
|
||||
Django backend is a thin hook into the Django test execution machinery. The
|
||||
unittest backend is a slightly less thin hook into the unittest modele. The
|
||||
key requirements of the executor backend are:
|
||||
|
||||
1. The ability to stream well-formed output to stdout
|
||||
2. The ability to limit/target test execution according to supplied labels
|
||||
|
||||
At the time of writing, sys.argv[1:] will be the list of dotted-namespaced
|
||||
names of tests which should be run. More complex command-line calls are simply
|
||||
not supported at this stage. A very useful task would be to do some more
|
||||
thinking on this interface.
|
36
cricket/docs/internals/contributing.rst
Normal file
36
cricket/docs/internals/contributing.rst
Normal file
|
@ -0,0 +1,36 @@
|
|||
Contributing to Cricket
|
||||
=======================
|
||||
|
||||
|
||||
If you experience problems with cricket, `log them on GitHub`_. If you want to contribute code, please `fork the code`_ and `submit a pull request`_.
|
||||
|
||||
.. _log them on Github: https://github.com/pybee/cricket/issues
|
||||
.. _fork the code: https://github.com/pybee/cricket
|
||||
.. _submit a pull request: https://github.com/pybee/cricket/pulls
|
||||
|
||||
|
||||
Setting up your development environment
|
||||
---------------------------------------
|
||||
|
||||
The recommended way of setting up your development envrionment for ``cricket``
|
||||
is to install a virtual environment, install the required dependencies and
|
||||
start coding. Assuming that you are using ``virtualenvwrapper``, you only have
|
||||
to run::
|
||||
|
||||
$ git clone git@github.com:pybee/cricket.git
|
||||
$ cd cricket
|
||||
$ mkvirtualenv cricket
|
||||
|
||||
Cricket uses ``unittest`` (or ``unittest2`` for Python < 2.7) for its own test
|
||||
suite as well as additional helper modules for testing. To install all the
|
||||
requirements for cricket, you have to run the following commands within your
|
||||
virtual environment::
|
||||
|
||||
$ pip install -e .
|
||||
$ pip install -r requirements_dev.txt
|
||||
|
||||
In case you are running a python version ``< 2.7`` please use the
|
||||
``requirements_dev_python2.7.txt`` instead because ``unittest2`` is not part
|
||||
of the standard library for these version.
|
||||
|
||||
Now you are ready to start hacking! Have fun!
|
32
cricket/docs/internals/roadmap.rst
Normal file
32
cricket/docs/internals/roadmap.rst
Normal file
|
@ -0,0 +1,32 @@
|
|||
Cricket Roadmap
|
||||
===============
|
||||
|
||||
Cricket is a new project - we have lots of things that we'd like to do. If
|
||||
you'd like to contribute, providing a patch for one of these features:
|
||||
|
||||
* Use a standard protocol (e.g., subunit) for communicating between the
|
||||
executor and the GUI
|
||||
|
||||
* Port to Python 3
|
||||
|
||||
* Add a pytest backend
|
||||
|
||||
* Add a nose backend
|
||||
|
||||
* Add a selenium backend, including possibly adding the ability to collect
|
||||
and store screenshots at the end of each test
|
||||
|
||||
* Improve GUI interface, including:
|
||||
|
||||
- keyboard shortcuts
|
||||
|
||||
- search
|
||||
|
||||
* Integrate with coverage, and use tkreadonly to display coverage stats
|
||||
overlaid on actual code
|
||||
|
||||
* Add historical tracking of test results
|
||||
|
||||
* Integrate with testr
|
||||
|
||||
* Add continuous background testing based on file modifications
|
190
cricket/docs/make.bat
Normal file
190
cricket/docs/make.bat
Normal file
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "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. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Django-Cricket.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Django-Cricket.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
62
cricket/docs/releases.rst
Normal file
62
cricket/docs/releases.rst
Normal file
|
@ -0,0 +1,62 @@
|
|||
Release History
|
||||
===============
|
||||
|
||||
0.2.4 - In development
|
||||
----------------------
|
||||
|
||||
0.2.3 - September 26, 2013
|
||||
--------------------------
|
||||
|
||||
* Added ability to generate coverage
|
||||
|
||||
* Integration with Duvet
|
||||
|
||||
0.2.2 - September 17, 2013
|
||||
--------------------------
|
||||
|
||||
* Corrected a problem with starting unittest2 projects.
|
||||
|
||||
* Corrected a error raised when unittest2 was manually installed in
|
||||
PYTHONPATH.
|
||||
|
||||
0.2.1 - September 14, 2013
|
||||
--------------------------
|
||||
|
||||
* Fixed selection of test modules in unittest2-style suite discovery.
|
||||
|
||||
* Added ability to run Django's own test suite.
|
||||
|
||||
0.2.0 - August 31, 2013
|
||||
-----------------------
|
||||
|
||||
* Relaunch as a PyBee project.
|
||||
|
||||
* Added test and help menus.
|
||||
|
||||
0.1.3 - July 29, 2013
|
||||
---------------------
|
||||
|
||||
* Corrected problem with Django test executor, masked by pyc caching.
|
||||
|
||||
* Improved collection of errors raised during test startup.
|
||||
|
||||
0.1.2 - July 26, 2013
|
||||
---------------------
|
||||
|
||||
* Improved handling of button state during test execution.
|
||||
|
||||
0.1.1 - July 9, 2013
|
||||
--------------------
|
||||
|
||||
Release incorporating updates from the PyCon AU 2013 sprints, including:
|
||||
|
||||
* Unittest2 support
|
||||
|
||||
* Improved handling of errors raised during test execution
|
||||
|
||||
* Improved reporting of errors caused on Ubuntu
|
||||
|
||||
0.1.0 - June 21, 2013
|
||||
---------------------
|
||||
|
||||
Initial public release, at PyCon AU 2013
|
1
cricket/requirements_dev.txt
Normal file
1
cricket/requirements_dev.txt
Normal file
|
@ -0,0 +1 @@
|
|||
mock==1.0.1
|
8
cricket/setup.cfg
Normal file
8
cricket/setup.cfg
Normal file
|
@ -0,0 +1,8 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
52
cricket/setup.py
Normal file
52
cricket/setup.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#/usr/bin/env python
|
||||
import io
|
||||
import re
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
with io.open('./cricket/__init__.py', encoding='utf8') as version_file:
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file.read(), re.M)
|
||||
if version_match:
|
||||
version = version_match.group(1)
|
||||
else:
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
with io.open('README.rst', encoding='utf8') as readme:
|
||||
long_description = readme.read()
|
||||
|
||||
|
||||
setup(
|
||||
name='cricket',
|
||||
version=version,
|
||||
description='A graphical tool to assist running test suites.',
|
||||
long_description=long_description,
|
||||
author='Russell Keith-Magee',
|
||||
author_email='russell@keith-magee.com',
|
||||
url='http://pybee.org/cricket',
|
||||
packages=find_packages(exclude='tests'),
|
||||
install_requires=['tkreadonly'],
|
||||
scripts=[],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'cricket-django = cricket.django.__main__:main',
|
||||
'cricket-unittest = cricket.unittest.__main__:main',
|
||||
]
|
||||
},
|
||||
license='New BSD',
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
test_suite='tests'
|
||||
)
|
0
cricket/tests/__init__.py
Normal file
0
cricket/tests/__init__.py
Normal file
851
cricket/tests/test_models.py
Normal file
851
cricket/tests/test_models.py
Normal file
|
@ -0,0 +1,851 @@
|
|||
from cricket.compat import unittest
|
||||
from cricket.model import Project, TestModule, TestCase
|
||||
|
||||
|
||||
class TestProject(unittest.TestCase):
|
||||
"""Tests for the process of converting the output of the Discoverer
|
||||
into an internal tree.
|
||||
"""
|
||||
def _full_tree(self, node):
|
||||
"Internal method generating a simple tree version of a project node"
|
||||
if isinstance(node, TestCase):
|
||||
return (type(node), node.keys())
|
||||
else:
|
||||
return dict(
|
||||
((type(sub_tree), sub_node), self._full_tree(sub_tree))
|
||||
for sub_node, sub_tree in node.items()
|
||||
)
|
||||
|
||||
def test_no_tests(self):
|
||||
"If there are no tests, an empty tree is generated"
|
||||
project = Project()
|
||||
project.refresh(test_list=[])
|
||||
self.assertEqual(project.errors, [])
|
||||
self.assertEqual(sorted(self._full_tree(project)), sorted({}))
|
||||
|
||||
def test_with_tests(self):
|
||||
"If tests are found, the right tree is created"
|
||||
|
||||
project = Project()
|
||||
project.refresh([
|
||||
'tests.FunkyTestCase.test_something_unnecessary',
|
||||
'more_tests.FunkyTestCase.test_this_does_make_sense',
|
||||
'more_tests.FunkyTestCase.test_this_doesnt_make_sense',
|
||||
'more_tests.JankyTestCase.test_things',
|
||||
'deep_tests.package.DeepTestCase.test_doo_hickey',
|
||||
])
|
||||
self.assertEqual(project.errors, [])
|
||||
self.assertEqual(sorted(self._full_tree(project)), sorted({
|
||||
(TestModule, 'tests'): {
|
||||
(TestCase, 'FunkyTestCase'): [
|
||||
'test_something_unnecessary'
|
||||
]
|
||||
},
|
||||
(TestModule, 'more_tests'): {
|
||||
(TestCase, 'FunkyTestCase'): [
|
||||
'test_this_doesnt_make_sense',
|
||||
'test_this_doesnt_make_sense'
|
||||
],
|
||||
(TestCase, 'JankyTestCase'): [
|
||||
'test_things'
|
||||
]
|
||||
},
|
||||
(TestModule, 'deep_tests'): {
|
||||
(TestModule, 'package'): {
|
||||
(TestCase, 'DeepTestCase'): [
|
||||
'test_doo_hickey'
|
||||
]
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
def test_with_tests_and_errors(self):
|
||||
"If tests *and* errors are found, the tree is still created."
|
||||
project = Project()
|
||||
project.refresh([
|
||||
'tests.FunkyTestCase.test_something_unnecessary',
|
||||
],
|
||||
errors=[
|
||||
'ERROR: you broke it, fool!',
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(project.errors, [
|
||||
'ERROR: you broke it, fool!',
|
||||
])
|
||||
self.assertEqual(sorted(self._full_tree(project)), sorted({
|
||||
(TestModule, 'tests'): {
|
||||
(TestCase, 'FunkyTestCase'): [
|
||||
'test_something_unnecessary'
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
class FindLabelTests(unittest.TestCase):
|
||||
"Check that naming tests by labels reduces to the right runtime list."
|
||||
def setUp(self):
|
||||
super(FindLabelTests, self).setUp()
|
||||
self.project = Project()
|
||||
self.project.refresh([
|
||||
'app1.TestCase.test_method',
|
||||
|
||||
'app2.TestCase1.test_method',
|
||||
'app2.TestCase2.test_method1',
|
||||
'app2.TestCase2.test_method2',
|
||||
|
||||
'app3.tests.TestCase.test_method',
|
||||
|
||||
'app4.tests1.TestCase.test_method',
|
||||
'app4.tests2.TestCase1.test_method',
|
||||
'app4.tests2.TestCase2.test_method1',
|
||||
'app4.tests2.TestCase2.test_method2',
|
||||
|
||||
'app5.package.tests.TestCase.test_method',
|
||||
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2.test_method1',
|
||||
'app6.package2.tests2.TestCase2.test_method2',
|
||||
|
||||
'app7.package.subpackage.tests.TestCase.test_method',
|
||||
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
])
|
||||
|
||||
def test_single_test_project(self):
|
||||
"If the project only contains a single test, the reduction is always the full suite"
|
||||
self.project = Project()
|
||||
self.project.refresh([
|
||||
'app.package.tests.TestCase.test_method',
|
||||
])
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app.package.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, []))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app.package.tests.TestCase'
|
||||
]),
|
||||
(1, []))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app.package.tests'
|
||||
]),
|
||||
(1, []))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app.package'
|
||||
]),
|
||||
(1, []))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app'
|
||||
]),
|
||||
(1, []))
|
||||
|
||||
def test_all_tests(self):
|
||||
"Without any qualifiers, all tests are run"
|
||||
self.assertEquals(self.project.find_tests(), (22, []))
|
||||
|
||||
def test_method_selection(self):
|
||||
"Explicitly named test method paths may be trimmed if they are unique"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app1.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app1']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1.test_method'
|
||||
]),
|
||||
(1, ['app2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase2.test_method1'
|
||||
]),
|
||||
(1, ['app2.TestCase2.test_method1']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app3.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app3']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app4.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase1.test_method'
|
||||
]),
|
||||
(1, ['app4.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase2.test_method1'
|
||||
]),
|
||||
(1, ['app4.tests2.TestCase2.test_method1']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app5.package.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app5']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app6.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app6.package2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase1.test_method'
|
||||
]),
|
||||
(1, ['app6.package2.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase2.test_method1'
|
||||
]),
|
||||
(1, ['app6.package2.tests2.TestCase2.test_method1']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7.package.subpackage.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app8.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests2.TestCase2.test_method1']))
|
||||
|
||||
def test_testcase_selection(self):
|
||||
"Explicitly named test case paths may be trimmed if they are unique"
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app1.TestCase'
|
||||
]),
|
||||
(1, ['app1']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1'
|
||||
]),
|
||||
(1, ['app2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase2'
|
||||
]),
|
||||
(2, ['app2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app3.tests.TestCase'
|
||||
]),
|
||||
(1, ['app3']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase'
|
||||
]),
|
||||
(1, ['app4.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase1'
|
||||
]),
|
||||
(1, ['app4.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase2'
|
||||
]),
|
||||
(2, ['app4.tests2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app5.package.tests.TestCase'
|
||||
]),
|
||||
(1, ['app5']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase'
|
||||
]),
|
||||
(1, ['app6.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1.TestCase'
|
||||
]),
|
||||
(1, ['app6.package2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase1'
|
||||
]),
|
||||
(1, ['app6.package2.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase2'
|
||||
]),
|
||||
(2, ['app6.package2.tests2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7.package.subpackage.tests.TestCase'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase'
|
||||
]),
|
||||
(1, ['app8.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests.TestCase'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1.TestCase'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase1'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests2.TestCase1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase2'
|
||||
]),
|
||||
(2, ['app8.package2.subpackage2.tests2.TestCase2']))
|
||||
|
||||
def test_testmodule_selection(self):
|
||||
"Explicitly named test module paths may be trimmed if they are unique"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app3.tests'
|
||||
]),
|
||||
(1, ['app3']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1'
|
||||
]),
|
||||
(1, ['app4.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2'
|
||||
]),
|
||||
(3, ['app4.tests2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app5.package.tests'
|
||||
]),
|
||||
(1, ['app5']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests'
|
||||
]),
|
||||
(1, ['app6.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1'
|
||||
]),
|
||||
(1, ['app6.package2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2'
|
||||
]),
|
||||
(3, ['app6.package2.tests2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7.package.subpackage.tests'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests'
|
||||
]),
|
||||
(1, ['app8.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage2.tests1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2'
|
||||
]),
|
||||
(3, ['app8.package2.subpackage2.tests2']))
|
||||
|
||||
def test_package_selection(self):
|
||||
"Explicitly named test package paths may be trimmed if they are unique"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app5.package'
|
||||
]),
|
||||
(1, ['app5']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1'
|
||||
]),
|
||||
(1, ['app6.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2'
|
||||
]),
|
||||
(4, ['app6.package2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7.package'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1'
|
||||
]),
|
||||
(1, ['app8.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2'
|
||||
]),
|
||||
(5, ['app8.package2']))
|
||||
|
||||
def test_subpackage_selection(self):
|
||||
"Explicitly named test subpackage paths may be trimmed if they are unique"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7.package.subpackage'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage'
|
||||
]),
|
||||
(1, ['app8.package1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1'
|
||||
]),
|
||||
(1, ['app8.package2.subpackage1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2'
|
||||
]),
|
||||
(4, ['app8.package2.subpackage2']))
|
||||
|
||||
def test_app_selection(self):
|
||||
"Explicitly named app paths return a count of all tests in the app"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app1'
|
||||
]),
|
||||
(1, ['app1']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2'
|
||||
]),
|
||||
(3, ['app2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app3'
|
||||
]),
|
||||
(1, ['app3']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4'
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app5'
|
||||
]),
|
||||
(1, ['app5']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6'
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app7'
|
||||
]),
|
||||
(1, ['app7']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8'
|
||||
]),
|
||||
(6, ['app8']))
|
||||
|
||||
def test_testcase_collapse(self):
|
||||
"If all methods in a test are selected, path is trimmed to the case"
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase2.test_method1',
|
||||
'app2.TestCase2.test_method2',
|
||||
]),
|
||||
(2, ['app2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase2.test_method1',
|
||||
'app4.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(2, ['app4.tests2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase2.test_method1',
|
||||
'app6.package2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(2, ['app6.package2.tests2.TestCase2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(2, ['app8.package2.subpackage2.tests2.TestCase2']))
|
||||
|
||||
def test_testmethod_collapse(self):
|
||||
"If all test cases in a test are selected, path is trimmed to the testmethod"
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1.test_method',
|
||||
'app2.TestCase2.test_method1',
|
||||
'app2.TestCase2.test_method2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1.test_method',
|
||||
'app2.TestCase2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1',
|
||||
'app2.TestCase2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase1.test_method',
|
||||
'app4.tests2.TestCase2.test_method1',
|
||||
'app4.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(3, ['app4.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase1.test_method',
|
||||
'app4.tests2.TestCase2',
|
||||
]),
|
||||
(3, ['app4.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests2.TestCase1',
|
||||
'app4.tests2.TestCase2',
|
||||
]),
|
||||
(3, ['app4.tests2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2.test_method1',
|
||||
'app6.package2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(3, ['app6.package2.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
'app6.package2.tests2',
|
||||
]),
|
||||
(3, ['app6.package2.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests2.TestCase1',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(3, ['app6.package2.tests2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(3, ['app8.package2.subpackage2.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(3, ['app8.package2.subpackage2.tests2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests2.TestCase1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(3, ['app8.package2.subpackage2.tests2']))
|
||||
|
||||
def test_package_collapse(self):
|
||||
"If all test cases in a test pacakge are selected, path is trimmed to the testmethod"
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2.test_method1',
|
||||
'app6.package2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(4, ['app6.package2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app6.package2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package2.tests1.TestCase',
|
||||
'app6.package2.tests2.TestCase1',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app6.package2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(5, ['app8.package2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(5, ['app8.package2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage1.tests.TestCase',
|
||||
'app8.package2.subpackage2.tests1.TestCase',
|
||||
'app8.package2.subpackage2.tests2.TestCase1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(5, ['app8.package2']))
|
||||
|
||||
def test_subpackage_collapse(self):
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(4, ['app8.package2.subpackage2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app8.package2.subpackage2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package2.subpackage2.tests1.TestCase',
|
||||
'app8.package2.subpackage2.tests2.TestCase1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app8.package2.subpackage2']))
|
||||
|
||||
def test_app_collapse(self):
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1.test_method',
|
||||
'app2.TestCase2.test_method1',
|
||||
'app2.TestCase2.test_method2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1.test_method',
|
||||
'app2.TestCase2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app2.TestCase1',
|
||||
'app2.TestCase2',
|
||||
]),
|
||||
(3, ['app2']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase.test_method',
|
||||
'app4.tests2.TestCase1.test_method',
|
||||
'app4.tests2.TestCase2.test_method1',
|
||||
'app4.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase.test_method',
|
||||
'app4.tests2.TestCase1.test_method',
|
||||
'app4.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase.test_method',
|
||||
'app4.tests2.TestCase1',
|
||||
'app4.tests2.TestCase2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase.test_method',
|
||||
'app4.tests2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1.TestCase',
|
||||
'app4.tests2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app4.tests1',
|
||||
'app4.tests2',
|
||||
]),
|
||||
(4, ['app4']))
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2.test_method1',
|
||||
'app6.package2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1.test_method',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2.TestCase1',
|
||||
'app6.package2.tests2.TestCase2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase.test_method',
|
||||
'app6.package2.tests2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1.TestCase',
|
||||
'app6.package2.tests2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2.tests1',
|
||||
'app6.package2.tests2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase.test_method',
|
||||
'app6.package2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests.TestCase',
|
||||
'app6.package2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1.tests',
|
||||
'app6.package2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app6.package1',
|
||||
'app6.package2',
|
||||
]),
|
||||
(5, ['app6']))
|
||||
|
||||
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2.test_method2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2.TestCase1',
|
||||
'app8.package2.subpackage2.tests2.TestCase2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1.TestCase',
|
||||
'app8.package2.subpackage2.tests2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2.tests1',
|
||||
'app8.package2.subpackage2.tests2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests.TestCase',
|
||||
'app8.package2.subpackage2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1.tests',
|
||||
'app8.package2.subpackage2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2.subpackage1',
|
||||
'app8.package2.subpackage2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase.test_method',
|
||||
'app8.package2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests.TestCase',
|
||||
'app8.package2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage.tests',
|
||||
'app8.package2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1.subpackage',
|
||||
'app8.package2',
|
||||
]),
|
||||
(6, ['app8']))
|
||||
self.assertEquals(self.project.find_tests(labels=[
|
||||
'app8.package1',
|
||||
'app8.package2',
|
||||
]),
|
||||
(6, ['app8']))
|
82
cricket/tests/test_unit_integration.py
Normal file
82
cricket/tests/test_unit_integration.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
import subprocess
|
||||
import unittest
|
||||
|
||||
import cricket
|
||||
|
||||
from cricket.unittest import discoverer
|
||||
from cricket.unittest import executor
|
||||
|
||||
class TestCollection(unittest.TestCase):
|
||||
|
||||
def test_testCollection(self):
|
||||
'''
|
||||
Confirm that the pytest discovery mechanism is capable of
|
||||
finding this test
|
||||
'''
|
||||
|
||||
PTD = discoverer.PyTestDiscoverer()
|
||||
PTD.collect_tests()
|
||||
tests = str(PTD).split('\n')
|
||||
|
||||
test_found = False
|
||||
for test in tests:
|
||||
test_found |= 'test_testCollection' in test
|
||||
self.assertTrue(test_found)
|
||||
|
||||
|
||||
class TestExecutorCmdLine(unittest.TestCase):
|
||||
|
||||
def test_labels(self):
|
||||
'''
|
||||
Test that the command-line API is respecting the labels
|
||||
being targetted for testing
|
||||
'''
|
||||
|
||||
labels = ['tests.test_unit_integration.TestCollection']
|
||||
cmdline = ['python', '-m', 'cricket.unittest.executor'] + labels
|
||||
|
||||
runner = subprocess.Popen(
|
||||
cmdline,
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
)
|
||||
|
||||
output = ''
|
||||
for line in runner.stdout:
|
||||
output += line.decode('utf-8')
|
||||
|
||||
self.assertIn('tests.test_unit_integration.TestCollection',
|
||||
output)
|
||||
self.assertNotIn('tests.test_unit_integration.TestExecutorCmdLine',
|
||||
output)
|
||||
|
||||
|
||||
|
||||
|
||||
# This is a magic test which can be un-commented and run manually.
|
||||
# It recursively calls the text executor, and fouls up normal
|
||||
# output, so it had to be disabled as I am not smart enough
|
||||
# to actually understand and fix the issue
|
||||
|
||||
# class TestExecutor(unittest.TestCase):
|
||||
|
||||
# def test_suite_execution(self):
|
||||
# '''
|
||||
# Note, it's hard to test full suite discovery because
|
||||
# it will include this test and infinite loop. So just
|
||||
# testing on a single test until I can figure out something
|
||||
# smarter.
|
||||
# '''
|
||||
|
||||
# run_only = [
|
||||
# 'tests.test_unit_integration.TestDiscoverer'
|
||||
# ]
|
||||
|
||||
# PTE = test_executor.PyTestExecutor()
|
||||
# PTE.run_only(run_only)
|
||||
# PTE.stream_results()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
12
gui-main
Executable file
12
gui-main
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
ROOT=$(dirname "$(readlink -f "$0")")
|
||||
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib/qt/plugins/platforms
|
||||
|
||||
export PYTHONPATH=$ROOT:$ROOT/cricket
|
||||
|
||||
pushd $ROOT/tests > /dev/null
|
||||
python -m cricket.unittest
|
||||
popd > /dev/null
|
BIN
res/nocturne.wav
Normal file
BIN
res/nocturne.wav
Normal file
Binary file not shown.
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
1
tests/auto/__init__.py
Normal file
1
tests/auto/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
MODULE_NAME = {'zh': '自动测试项', 'en': 'Auto Test Item'}
|
19
tests/auto/test_01_nvme_ssd.py
Normal file
19
tests/auto/test_01_nvme_ssd.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
|
||||
class NVMeSSDTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'NVMeSSDTest': 'NVMe固态硬盘',
|
||||
'test_read_model': '读取型号'
|
||||
},
|
||||
'en': {
|
||||
'NVMeSSDTest': 'NVMe SSD',
|
||||
'test_read_model': 'Read model'
|
||||
}
|
||||
}
|
||||
|
||||
def test_read_model(self):
|
||||
model_file = '/sys/class/nvme/nvme0/model'
|
||||
self.assertTrue(os.path.exists(model_file))
|
0
tests/auto/test_02_4g_module.py
Normal file
0
tests/auto/test_02_4g_module.py
Normal file
0
tests/auto/test_03_mini_pcie_ssd.py
Normal file
0
tests/auto/test_03_mini_pcie_ssd.py
Normal file
20
tests/auto/test_04_mouse.py
Normal file
20
tests/auto/test_04_mouse.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
|
||||
class MouseTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'MouseTest': '鼠标',
|
||||
'test_read_product': '识别产品名称'
|
||||
},
|
||||
'en': {
|
||||
'MouseTest': 'Mouse',
|
||||
'test_read_product': 'Read product name'
|
||||
}
|
||||
}
|
||||
|
||||
def test_read_product(self):
|
||||
u2_hub_dir = '/sys/devices/platform/soc/soc:usb3@0/c0a00000.dwc3/xhci-hcd.0.auto/usb2/2-1/'
|
||||
self.assertTrue(os.path.exists(f'{u2_hub_dir}/product'))
|
||||
self.assertTrue(os.path.exists(f'{u2_hub_dir}/2-1.1/product'))
|
24
tests/auto/test_04_udisk.py
Normal file
24
tests/auto/test_04_udisk.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
|
||||
class UDiskTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'UDiskTest': 'U盘',
|
||||
'test_read_product': '识别产品名称'
|
||||
},
|
||||
'en': {
|
||||
'UDiskTest': 'U Disk',
|
||||
'test_read_product': 'Read product name'
|
||||
}
|
||||
}
|
||||
|
||||
def test_read_product(self):
|
||||
u2_hub_dir = '/sys/devices/platform/soc/soc:usb3@0/c0a00000.dwc3/xhci-hcd.0.auto/usb2/2-1/'
|
||||
u3_hub_dir = '/sys/devices/platform/soc/soc:usb3@0/c0a00000.dwc3/xhci-hcd.0.auto/usb3/3-1/'
|
||||
self.assertTrue(os.path.exists(f'{u2_hub_dir}/product'))
|
||||
self.assertTrue(os.path.exists(f'{u3_hub_dir}/product'))
|
||||
# self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.2/product'))
|
||||
# self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.3/product'))
|
||||
self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.4/product'))
|
19
tests/auto/test_05_emmc.py
Normal file
19
tests/auto/test_05_emmc.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
|
||||
class eMMCTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'eMMCTest': 'eMMC',
|
||||
'test_identify': '识别'
|
||||
},
|
||||
'en': {
|
||||
'eMMCTest': 'eMMC',
|
||||
'test_identify': 'Identify'
|
||||
}
|
||||
}
|
||||
|
||||
def test_identify(self):
|
||||
block_file = '/sys/class/block/mmcblk2'
|
||||
self.assertTrue(os.path.exists(block_file))
|
0
tests/auto/test_06_eeprom.py
Normal file
0
tests/auto/test_06_eeprom.py
Normal file
69
tests/auto/test_07_eth0.py
Normal file
69
tests/auto/test_07_eth0.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import struct
|
||||
import fcntl
|
||||
import subprocess
|
||||
|
||||
class Eth0Test(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'Eth0Test': '网口1',
|
||||
'test_ping': 'ping'
|
||||
},
|
||||
'en': {
|
||||
'Eth0Test': '网口1',
|
||||
'test_ping': 'ping'
|
||||
}
|
||||
}
|
||||
|
||||
def get_carrier(self, ifname: str):
|
||||
carrier = f'/sys/class/net/{ifname}/carrier'
|
||||
|
||||
with open(carrier, 'r') as f:
|
||||
return f.readline().strip()
|
||||
|
||||
def get_ip(self, ifname: str):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8915,
|
||||
struct.pack('256s', bytes(ifname[:15],'utf-8'))
|
||||
)[20:24])
|
||||
|
||||
def test_ping(self):
|
||||
ifname = 'eth0'
|
||||
ping = f'ping -I {ifname} -c 3 baidu.com'
|
||||
timeout = 10
|
||||
|
||||
i = 0
|
||||
while i < timeout:
|
||||
if self.get_carrier(ifname) == '1':
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
|
||||
if i == timeout:
|
||||
self.fail('Get carrier imeout')
|
||||
|
||||
i = 0
|
||||
while i < timeout:
|
||||
try:
|
||||
ip = self.get_ip(ifname)
|
||||
print(f'{ifname}: {ip}')
|
||||
break
|
||||
except:
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
|
||||
if i == timeout:
|
||||
self.fail('Get ip timeout')
|
||||
|
||||
try:
|
||||
result = subprocess.run(ping, capture_output=True, shell=True, timeout=timeout)
|
||||
self.assertEqual(result.returncode, 0)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.fail('Ping timeout')
|
69
tests/auto/test_07_eth1.py
Normal file
69
tests/auto/test_07_eth1.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import struct
|
||||
import fcntl
|
||||
import subprocess
|
||||
|
||||
class Eth1Test(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'Eth1Test': '网口2',
|
||||
'test_ping': 'ping'
|
||||
},
|
||||
'en': {
|
||||
'Eth1Test': '网口2',
|
||||
'test_ping': 'ping'
|
||||
}
|
||||
}
|
||||
|
||||
def get_carrier(self, ifname: str):
|
||||
carrier = f'/sys/class/net/{ifname}/carrier'
|
||||
|
||||
with open(carrier, 'r') as f:
|
||||
return f.readline().strip()
|
||||
|
||||
def get_ip(self, ifname: str):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8915,
|
||||
struct.pack('256s', bytes(ifname[:15],'utf-8'))
|
||||
)[20:24])
|
||||
|
||||
def test_ping(self):
|
||||
ifname = 'eth1'
|
||||
ping = f'ping -I {ifname} -c 3 baidu.com'
|
||||
timeout = 10
|
||||
|
||||
i = 0
|
||||
while i < timeout:
|
||||
if self.get_carrier(ifname) == '1':
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
|
||||
if i == timeout:
|
||||
self.fail('Get carrier imeout')
|
||||
|
||||
i = 0
|
||||
while i < timeout:
|
||||
try:
|
||||
ip = self.get_ip(ifname)
|
||||
print(f'{ifname}: {ip}')
|
||||
break
|
||||
except:
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
|
||||
if i == timeout:
|
||||
self.fail('Get ip timeout')
|
||||
|
||||
try:
|
||||
result = subprocess.run(ping, capture_output=True, shell=True, timeout=timeout)
|
||||
self.assertEqual(result.returncode, 0)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.fail('Ping timeout')
|
23
tests/auto/test_08_bt.py
Normal file
23
tests/auto/test_08_bt.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import subprocess
|
||||
|
||||
class BTTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'BTTest': '蓝牙',
|
||||
'test_scan': '扫描'
|
||||
},
|
||||
'en': {
|
||||
'BTTest': 'Bluetooth',
|
||||
'test_scan': 'Scan'
|
||||
}
|
||||
}
|
||||
|
||||
def test_scan(self):
|
||||
scan = 'hcitool -i hci0 scan'
|
||||
try:
|
||||
result = subprocess.run(scan, capture_output=True, shell=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 0)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.fail('Scan timeout')
|
1
tests/manual/__init__.py
Normal file
1
tests/manual/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
MODULE_NAME = {'zh': '手动测试项', 'en': 'Manual Test Item'}
|
48
tests/manual/test_04_speaker.py
Normal file
48
tests/manual/test_04_speaker.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from utils.speaker import SpeakerTestWindow, GetTestResult
|
||||
|
||||
class SpeakerTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'SpeakerTest': '喇叭',
|
||||
'test_play': '播放一段铃声',
|
||||
'title': '喇叭',
|
||||
'test_step': '''1. 点击播放,设备将播放一段铃声,判断从喇叭听到的铃声是否符合预期
|
||||
'''
|
||||
},
|
||||
'en': {
|
||||
'SpeakerTest': 'Speaker',
|
||||
'test_play': 'Paly bell',
|
||||
'title': 'Speaker',
|
||||
'test_step': '''1. 点击播放,设备将播放一段铃声,判断从喇叭听到的铃声是否符合预期
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
def _play_routine(self):
|
||||
volume = 128
|
||||
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="DAC Playback Volume" {volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'amixer: {result.returncode}')
|
||||
|
||||
bell_file = '/opt/factorytest/res/nocturne.wav'
|
||||
cmd = f'aplay -Dhw:1,0 -r 48000 -f S16_LE {bell_file} > /dev/null'
|
||||
self.play_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_routine(self):
|
||||
if self.play_proc.poll() is None:
|
||||
print('Try to kill play process...')
|
||||
self.play_proc.kill()
|
||||
|
||||
def test_play(self):
|
||||
t = threading.Thread(target=SpeakerTestWindow,
|
||||
args=(self._play_routine,
|
||||
self._stop_routine,
|
||||
self.LANGUAGES,))
|
||||
t.start()
|
||||
t.join()
|
||||
self.assertTrue(GetTestResult())
|
67
tests/manual/test_05_mic.py
Normal file
67
tests/manual/test_05_mic.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from utils.mic import MicTestWindow, GetTestResult
|
||||
|
||||
class MicTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'MicTest': '麦克风',
|
||||
'test_mic_loop': '录音回放',
|
||||
'title': '麦克风',
|
||||
'test_step': '''1. 点击录音,对着开发板说:测试1、2、3、4、5
|
||||
2. 点击停止,判断从喇叭听到的是否刚才对着开发板说的
|
||||
'''
|
||||
},
|
||||
'en': {
|
||||
'MicTest': 'Mic',
|
||||
'test_mic_loop': 'Record and playback',
|
||||
'title': 'Mic',
|
||||
'test_step': '''1. 点击录音,对着开发板说:测试1、2、3、4、5
|
||||
2. 点击停止,判断从喇叭听到的是否刚才对着开发板说的
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
def _record_routine(self):
|
||||
volume = 191
|
||||
cmd = f'amixer -c 1 cset numid=7,iface=MIXER,name="ADC Capture Volume" {volume},{volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'capture amixer: {result.returncode}')
|
||||
|
||||
timeout = 10
|
||||
record_file = '/tmp/record.wav'
|
||||
cmd = f'arecord -Dhw:1,0 -r 48000 -f S16_LE -d {timeout} {record_file}'
|
||||
self.record_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_record(self):
|
||||
if self.record_proc.poll() is None:
|
||||
print('Try to kill record process...')
|
||||
self.record_proc.kill()
|
||||
|
||||
def _playback_routine(self):
|
||||
volume = 191
|
||||
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="DAC Playback Volume" {volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'playback amixer: {result.returncode}')
|
||||
|
||||
cmd = 'aplay -Dhw:1,0 -r 48000 -f S16_LE /tmp/record.wav'
|
||||
self.playback_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_playback(self):
|
||||
if self.playback_proc.poll() is None:
|
||||
print('Try to kill playback process...')
|
||||
self.playback_proc.kill()
|
||||
|
||||
def test_mic_loop(self):
|
||||
t = threading.Thread(target=MicTestWindow,
|
||||
args=(self._record_routine,
|
||||
self._stop_record,
|
||||
self._playback_routine,
|
||||
self._stop_playback,
|
||||
self.LANGUAGES),)
|
||||
t.start()
|
||||
t.join()
|
||||
self.assertTrue(GetTestResult())
|
50
tests/manual/test_06_headphones.py
Normal file
50
tests/manual/test_06_headphones.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from utils.speaker import SpeakerTestWindow, GetTestResult
|
||||
|
||||
class HeadphonesTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'HeadphonesTest': '耳机',
|
||||
'test_play': '播放一段铃声',
|
||||
'title': '耳机',
|
||||
'test_step': '''1. 插入耳机
|
||||
2. 点击播放,设备将播放一段铃声,判断从喇叭听到的铃声是否符合预期
|
||||
'''
|
||||
},
|
||||
'en': {
|
||||
'HeadphonesTest': 'Headphones',
|
||||
'test_play': 'Paly bell',
|
||||
'title': 'Headphones',
|
||||
'test_step': '''1. 插入耳机
|
||||
2. 点击播放,设备将播放一段铃声,判断从喇叭听到的铃声是否符合预期
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
def _play_routine(self):
|
||||
volume = 128
|
||||
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="DAC Playback Volume" {volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'amixer: {result.returncode}')
|
||||
|
||||
bell_file = '/opt/factorytest/res/nocturne.wav'
|
||||
cmd = f'aplay -Dhw:1,0 -r 48000 -f S16_LE {bell_file}'
|
||||
self.play_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_routine(self):
|
||||
if self.play_proc.poll() is None:
|
||||
print('Try to kill play process...')
|
||||
self.play_proc.kill()
|
||||
|
||||
def test_play(self):
|
||||
t = threading.Thread(target=SpeakerTestWindow,
|
||||
args=(self._play_routine,
|
||||
self._stop_routine,
|
||||
self.LANGUAGES),)
|
||||
t.start()
|
||||
t.join()
|
||||
self.assertTrue(GetTestResult())
|
69
tests/manual/test_07_microphone.py
Normal file
69
tests/manual/test_07_microphone.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from unittest import TestCase
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from utils.mic import MicTestWindow, GetTestResult
|
||||
|
||||
class MicrophoneTest(TestCase):
|
||||
LANGUAGES = {
|
||||
'zh': {
|
||||
'MicrophoneTest': '耳麦',
|
||||
'test_mic_loop': '录音回放',
|
||||
'title': '耳麦',
|
||||
'test_step': '''1. 插入耳机
|
||||
2. 点击录音,对着耳麦说:测试1、2、3、4、5
|
||||
3. 点击停止,判断从喇叭听到的是否刚才对着开发板说的
|
||||
'''
|
||||
},
|
||||
'en': {
|
||||
'MicrophoneTest': 'Microphone',
|
||||
'test_mic_loop': 'Record and playback',
|
||||
'title': 'Microphone',
|
||||
'test_step': '''1. 插入耳机
|
||||
2. 点击录音,对着耳麦说:测试1、2、3、4、5
|
||||
3. 点击停止,判断从喇叭听到的是否刚才对着开发板说的
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
def _record_routine(self):
|
||||
volume = 191
|
||||
cmd = f'amixer -c 1 cset numid=7,iface=MIXER,name="ADC Capture Volume" {volume},{volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'capture amixer: {result.returncode}')
|
||||
|
||||
timeout = 10
|
||||
record_file = '/tmp/record.wav'
|
||||
cmd = f'arecord -Dhw:1,0 -r 48000 -f S16_LE -d {timeout} {record_file}'
|
||||
self.record_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_record(self):
|
||||
if self.record_proc.poll() is None:
|
||||
print('Try to kill record process...')
|
||||
self.record_proc.kill()
|
||||
|
||||
def _playback_routine(self):
|
||||
volume = 160
|
||||
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="DAC Playback Volume" {volume}'
|
||||
result = subprocess.run(cmd, capture_output=True, shell=True)
|
||||
print(f'playback amixer: {result.returncode}')
|
||||
|
||||
cmd = 'aplay -Dhw:1,0 -r 48000 -f S16_LE /tmp/record.wav'
|
||||
self.playback_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
def _stop_playback(self):
|
||||
if self.playback_proc.poll() is None:
|
||||
print('Try to kill playback process...')
|
||||
self.playback_proc.kill()
|
||||
|
||||
def test_mic_loop(self):
|
||||
t = threading.Thread(target=MicTestWindow,
|
||||
args=(self._record_routine,
|
||||
self._stop_record,
|
||||
self._playback_routine,
|
||||
self._stop_playback,
|
||||
self.LANGUAGES),)
|
||||
t.start()
|
||||
t.join()
|
||||
self.assertTrue(GetTestResult())
|
137
utils/mic.py
Normal file
137
utils/mic.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QGuiApplication
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QFrame,
|
||||
QVBoxLayout,
|
||||
QGroupBox,
|
||||
QLabel,
|
||||
QStatusBar,
|
||||
QPushButton,
|
||||
QGridLayout
|
||||
)
|
||||
|
||||
from cricket.lang import SimpleLang
|
||||
|
||||
result = False
|
||||
|
||||
class MicTestWindow(SimpleLang):
|
||||
def __init__(self, record_routine, stop_record, playback_routine, stop_playback, languages) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.record_routine = record_routine
|
||||
self.stop_record = stop_record
|
||||
self.playback_routine = playback_routine
|
||||
self.stop_playback = stop_playback
|
||||
self.languages = languages
|
||||
|
||||
self.app = QApplication([])
|
||||
|
||||
self.window = QMainWindow()
|
||||
self.window.setWindowTitle(self._get_text('title'))
|
||||
# self.window.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||||
|
||||
# Main content
|
||||
content = QFrame(self.window)
|
||||
content_layout = QVBoxLayout(content)
|
||||
|
||||
# Test step
|
||||
box = QGroupBox(self.get_text('test_step'), content)
|
||||
box_layout = QVBoxLayout(box)
|
||||
|
||||
label = QLabel(self._get_text('test_step'), box)
|
||||
box_layout.addWidget(label)
|
||||
|
||||
content_layout.addWidget(box)
|
||||
|
||||
# Status
|
||||
self.status = QStatusBar(content)
|
||||
self.status.showMessage('Not running')
|
||||
content_layout.addWidget(self.status)
|
||||
|
||||
# Toolbar
|
||||
toolbar = QFrame(content)
|
||||
toolbar_layout = QGridLayout(toolbar)
|
||||
|
||||
self.record_button = QPushButton(self.get_text('record_button'), toolbar)
|
||||
self.record_button.clicked.connect(self.cmd_record)
|
||||
self.record_button.setFocus()
|
||||
toolbar_layout.addWidget(self.record_button, 0, 0)
|
||||
|
||||
self.playback_button = QPushButton(self.get_text('playback_button'), toolbar)
|
||||
self.playback_button.setDisabled(True)
|
||||
self.playback_button.clicked.connect(self.cmd_playback)
|
||||
toolbar_layout.addWidget(self.playback_button, 0, 1)
|
||||
|
||||
self.pass_button = QPushButton(self.get_text('pass_button'), toolbar)
|
||||
self.pass_button.setDisabled(True)
|
||||
self.pass_button.clicked.connect(self.cmd_pass)
|
||||
toolbar_layout.addWidget(self.pass_button, 0, 2)
|
||||
|
||||
self.fail_button = QPushButton(self.get_text('fail_button'), toolbar)
|
||||
self.fail_button.setDisabled(True)
|
||||
self.fail_button.clicked.connect(self.cmd_fail)
|
||||
toolbar_layout.addWidget(self.fail_button, 0, 3)
|
||||
|
||||
content_layout.addWidget(toolbar)
|
||||
|
||||
self.window.setCentralWidget(content)
|
||||
|
||||
# Move to center
|
||||
# screen = QGuiApplication.primaryScreen()
|
||||
# if screen:
|
||||
# screen_geometry = screen.geometry()
|
||||
# window_geometry = self.window.geometry()
|
||||
# x = (screen_geometry.width() - window_geometry.width()) // 2
|
||||
# y = (screen_geometry.height() - window_geometry.height()) // 2
|
||||
# self.window.move(x, y)
|
||||
|
||||
# self.window.show()
|
||||
self.window.showFullScreen()
|
||||
|
||||
self.app.exec_()
|
||||
|
||||
def _get_text(self, key):
|
||||
if self.languages:
|
||||
lang = self.languages.get(self.current_lang)
|
||||
if lang is not None:
|
||||
text = lang.get(key)
|
||||
if text is not None:
|
||||
return text
|
||||
|
||||
return 'Undefined'
|
||||
|
||||
def cmd_record(self):
|
||||
self.status.showMessage('Recording...')
|
||||
self.record_button.setDisabled(True)
|
||||
self.record_routine()
|
||||
self.playback_button.setDisabled(False)
|
||||
|
||||
def cmd_playback(self):
|
||||
self.stop_record()
|
||||
|
||||
self.status.showMessage('Playback...')
|
||||
self.playback_button.setDisabled(True)
|
||||
self.playback_routine()
|
||||
self.pass_button.setDisabled(False)
|
||||
self.fail_button.setDisabled(False)
|
||||
|
||||
def cmd_pass(self):
|
||||
global result
|
||||
|
||||
self.status.showMessage('Stopping...')
|
||||
self.stop_playback()
|
||||
result = True
|
||||
self.app.quit()
|
||||
|
||||
def cmd_fail(self):
|
||||
global result
|
||||
|
||||
self.status.showMessage('Stopping...')
|
||||
self.stop_playback()
|
||||
result = False
|
||||
self.app.quit()
|
||||
|
||||
def GetTestResult():
|
||||
return result
|
135
utils/speaker.py
Normal file
135
utils/speaker.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QGuiApplication
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QFrame,
|
||||
QVBoxLayout,
|
||||
QGroupBox,
|
||||
QLabel,
|
||||
QStatusBar,
|
||||
QPushButton,
|
||||
QGridLayout
|
||||
)
|
||||
|
||||
from cricket.lang import SimpleLang
|
||||
|
||||
result = False
|
||||
|
||||
class SpeakerTestWindow(SimpleLang):
|
||||
def __init__(self, play_routine, stop_routine, languages) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.play_routine = play_routine
|
||||
self.stop_routine = stop_routine
|
||||
self.languages = languages
|
||||
|
||||
self.app = QApplication([])
|
||||
|
||||
self.window = QMainWindow()
|
||||
self.window.setWindowTitle(self._get_text('title'))
|
||||
# self.window.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||||
|
||||
# Main content
|
||||
content = QFrame(self.window)
|
||||
content_layout = QVBoxLayout(content)
|
||||
|
||||
# Test step
|
||||
box = QGroupBox(self.get_text('test_step'), content)
|
||||
box_layout = QVBoxLayout(box)
|
||||
|
||||
label = QLabel(self._get_text('test_step'), box)
|
||||
box_layout.addWidget(label)
|
||||
|
||||
content_layout.addWidget(box)
|
||||
|
||||
# Status
|
||||
self.status = QStatusBar(content)
|
||||
self.status.showMessage('Not running')
|
||||
content_layout.addWidget(self.status)
|
||||
|
||||
# Toolbar
|
||||
toolbar = QFrame(content)
|
||||
toolbar_layout = QGridLayout(toolbar)
|
||||
|
||||
self.play_button = QPushButton(self.get_text('play_button'), toolbar)
|
||||
self.play_button.clicked.connect(self.cmd_play)
|
||||
self.play_button.setFocus()
|
||||
toolbar_layout.addWidget(self.play_button, 0, 0)
|
||||
|
||||
self.stop_button = QPushButton(self.get_text('stop_button'), toolbar)
|
||||
self.stop_button.setDisabled(True)
|
||||
self.stop_button.clicked.connect(self.cmd_stop)
|
||||
toolbar_layout.addWidget(self.stop_button, 0, 1)
|
||||
|
||||
self.pass_button = QPushButton(self.get_text('pass_button'), toolbar)
|
||||
self.pass_button.setDisabled(True)
|
||||
self.pass_button.clicked.connect(self.cmd_pass)
|
||||
toolbar_layout.addWidget(self.pass_button, 0, 2)
|
||||
|
||||
self.fail_button = QPushButton(self.get_text('fail_button'), toolbar)
|
||||
self.fail_button.setDisabled(True)
|
||||
self.fail_button.clicked.connect(self.cmd_fail)
|
||||
toolbar_layout.addWidget(self.fail_button, 0, 3)
|
||||
|
||||
content_layout.addWidget(toolbar)
|
||||
|
||||
self.window.setCentralWidget(content)
|
||||
|
||||
# Move to center
|
||||
# screen = QGuiApplication.primaryScreen()
|
||||
# if screen:
|
||||
# screen_geometry = screen.geometry()
|
||||
# window_geometry = self.window.geometry()
|
||||
# x = (screen_geometry.width() - window_geometry.width()) // 2
|
||||
# y = (screen_geometry.height() - window_geometry.height()) // 2
|
||||
# self.window.move(x, y)
|
||||
|
||||
# self.window.show()
|
||||
self.window.showFullScreen()
|
||||
|
||||
self.app.exec_()
|
||||
|
||||
def _get_text(self, key):
|
||||
if self.languages:
|
||||
lang = self.languages.get(self.current_lang)
|
||||
if lang is not None:
|
||||
text = lang.get(key)
|
||||
if text is not None:
|
||||
return text
|
||||
|
||||
return 'Undefined'
|
||||
|
||||
def cmd_play(self):
|
||||
self.status.showMessage('Running...')
|
||||
self.play_button.setDisabled(True)
|
||||
self.play_routine()
|
||||
self.stop_button.setDisabled(False)
|
||||
self.pass_button.setDisabled(False)
|
||||
self.fail_button.setDisabled(False)
|
||||
|
||||
def cmd_stop(self):
|
||||
self.status.showMessage('Stopping...')
|
||||
self.stop_button.setDisabled(True)
|
||||
self.stop_routine()
|
||||
self.play_button.setDisabled(False)
|
||||
self.status.showMessage('Stopped')
|
||||
|
||||
def cmd_pass(self):
|
||||
global result
|
||||
|
||||
self.status.showMessage('Stopping...')
|
||||
self.stop_routine()
|
||||
result = True
|
||||
self.app.quit()
|
||||
|
||||
def cmd_fail(self):
|
||||
global result
|
||||
|
||||
self.status.showMessage('Stopping...')
|
||||
self.stop_routine()
|
||||
result = False
|
||||
self.app.quit()
|
||||
|
||||
def GetTestResult():
|
||||
return result
|
Loading…
Add table
Reference in a new issue