mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 706844 - Create a make target for peptest. r=jmaher
This commit is contained in:
parent
15b4352a07
commit
e5f5fd2c69
@ -66,6 +66,12 @@ MOZBASE_EXTRAS = \
|
||||
README \
|
||||
$(NULL)
|
||||
|
||||
_DEST_DIR = $(DEPTH)/_tests/mozbase
|
||||
libs:: $(MOZBASE_PACKAGES)
|
||||
$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
|
||||
libs:: $(MOZBASE_EXTRAS)
|
||||
$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
|
||||
|
||||
stage-package: PKG_STAGE = $(DIST)/test-package-stage
|
||||
stage-package:
|
||||
$(NSINSTALL) -D $(PKG_STAGE)/mozbase
|
||||
|
@ -1,33 +1,27 @@
|
||||
ManifestDestiny
|
||||
===============
|
||||
|
||||
Universal manifests for Mozilla test harnesses
|
||||
|
||||
# What is ManifestDestiny?
|
||||
|
||||
What is ManifestDestiny?
|
||||
------------------------
|
||||
|
||||
What ManifestDestiny gives you::
|
||||
What ManifestDestiny gives you:
|
||||
|
||||
* manifests are (ordered) lists of tests
|
||||
* tests may have an arbitrary number of key, value pairs
|
||||
* the parser returns an ordered list of test data structures, which
|
||||
are just dicts with some keys. For example, a test with no
|
||||
user-specified metadata looks like this::
|
||||
user-specified metadata looks like this:
|
||||
|
||||
[{'path':
|
||||
'/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests/testToolbar/testBackForwardButtons.js',
|
||||
'name': 'testToolbar/testBackForwardButtons.js', 'here':
|
||||
'/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',
|
||||
'manifest': '/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',}]
|
||||
[{'path':
|
||||
'/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests/testToolbar/testBackForwardButtons.js',
|
||||
'name': 'testToolbar/testBackForwardButtons.js', 'here':
|
||||
'/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',
|
||||
'manifest': '/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',}]
|
||||
|
||||
The keys displayed here (path, name, here, and manifest) are reserved
|
||||
keys for ManifestDestiny and any consuming APIs. You can add
|
||||
additional key, value metadata to each test.
|
||||
|
||||
|
||||
Why have test manifests?
|
||||
------------------------
|
||||
# Why have test manifests?
|
||||
|
||||
Most Mozilla test harnesses work by crawling a directory structure.
|
||||
While this is straight-forward, manifests offer several practical
|
||||
@ -41,7 +35,7 @@ advantages::
|
||||
one around that knows the test?), then backing out a test is at best
|
||||
problematic. With a manifest, a test may be disabled without
|
||||
removing it from the tree and a bug filed with the appropriate
|
||||
reason::
|
||||
reason:
|
||||
|
||||
[test_broken.js]
|
||||
disabled = https://bugzilla.mozilla.org/show_bug.cgi?id=123456
|
||||
@ -66,11 +60,10 @@ advantages::
|
||||
(sub)manifests as appropriate to your needs.
|
||||
|
||||
|
||||
Manifest Format
|
||||
---------------
|
||||
# Manifest Format
|
||||
|
||||
Manifests are .ini file with the section names denoting the path
|
||||
relative to the manifest::
|
||||
relative to the manifest:
|
||||
|
||||
[foo.js]
|
||||
[bar.js]
|
||||
@ -78,8 +71,8 @@ relative to the manifest::
|
||||
|
||||
The sections are read in order. In addition, tests may include
|
||||
arbitrary key, value metadata to be used by the harness. You may also
|
||||
have a ``[DEFAULT]`` section that will give key, value pairs that will
|
||||
be inherited by each test unless overridden::
|
||||
have a `[DEFAULT]` section that will give key, value pairs that will
|
||||
be inherited by each test unless overridden:
|
||||
|
||||
[DEFAULT]
|
||||
type = restart
|
||||
@ -95,19 +88,18 @@ be inherited by each test unless overridden::
|
||||
[roses.js]
|
||||
color = red
|
||||
|
||||
You can also include other manifests::
|
||||
You can also include other manifests:
|
||||
|
||||
[include:subdir/anothermanifest.ini]
|
||||
|
||||
Manifests are included relative to the directory of the manifest with
|
||||
the ``[include:]`` directive unless they are absolute paths.
|
||||
the `[include:]` directive unless they are absolute paths.
|
||||
|
||||
|
||||
Data
|
||||
----
|
||||
# Data
|
||||
|
||||
Manifest Destiny gives tests as a list of dictionaries (in python
|
||||
terms).
|
||||
terms).
|
||||
|
||||
* path: full path to the test
|
||||
* name: short name of the test; this is the (usually) relative path
|
||||
@ -115,7 +107,7 @@ terms).
|
||||
* here: the parent directory of the manifest
|
||||
* manifest: the path to the manifest containing the test
|
||||
|
||||
This data corresponds to a one-line manifest::
|
||||
This data corresponds to a one-line manifest:
|
||||
|
||||
[testToolbar/testBackForwardButtons.js]
|
||||
|
||||
@ -125,7 +117,7 @@ as well.
|
||||
Outside of the reserved keys, the remaining key, values
|
||||
are up to convention to use. There is a (currently very minimal)
|
||||
generic integration layer in ManifestDestiny for use of all harnesses,
|
||||
``manifestparser.TestManifest``.
|
||||
`manifestparser.TestManifest`.
|
||||
For instance, if the 'disabled' key is present, you can get the set of
|
||||
tests without disabled (various other queries are doable as well).
|
||||
|
||||
@ -134,7 +126,7 @@ they want with the data. They may ignore it completely, they may use
|
||||
the provided integration layer, or they may provide their own
|
||||
integration layer. This should allow whatever sort of logic is
|
||||
desired. For instance, if in yourtestharness you wanted to run only on
|
||||
mondays for a certain class of tests::
|
||||
mondays for a certain class of tests:
|
||||
|
||||
tests = []
|
||||
for test in manifests.tests:
|
||||
@ -150,144 +142,133 @@ To recap:
|
||||
* you can use it however you want or process it further as you need
|
||||
|
||||
Tests are denoted by sections in an .ini file (see
|
||||
http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestdestiny/tests/mozmill-example.ini).
|
||||
http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestdestiny/tests/mozmill-example.ini).
|
||||
|
||||
Additional manifest files may be included with an ``[include:]`` directive::
|
||||
Additional manifest files may be included with an `[include:]` directive:
|
||||
|
||||
[include:path-to-additional-file.manifest]
|
||||
|
||||
The path to included files is relative to the current manifest.
|
||||
|
||||
The ``[DEFAULT]`` section contains variables that all tests inherit from.
|
||||
The `[DEFAULT]` section contains variables that all tests inherit from.
|
||||
|
||||
Included files will inherit the top-level variables but may override
|
||||
in their own ``[DEFAULT]`` section.
|
||||
in their own `[DEFAULT]` section.
|
||||
|
||||
|
||||
ManifestDestiny Architecture
|
||||
----------------------------
|
||||
# ManifestDestiny Architecture
|
||||
|
||||
There is a two- or three-layered approach to the ManifestDestiny
|
||||
architecture, depending on your needs::
|
||||
architecture, depending on your needs:
|
||||
|
||||
1. ManifestParser: this is a generic parser for .ini manifests that
|
||||
facilitates the `[include:]` logic and the inheritence of
|
||||
metadata. Despite the internal variable being called ``self.tests``
|
||||
metadata. Despite the internal variable being called `self.tests`
|
||||
(an oversight), this layer has nothing in particular to do with tests.
|
||||
|
||||
2. TestManifest: this is a harness-agnostic integration layer that is
|
||||
test-specific. TestManifest faciliates ``skip-if`` and ``run-if``
|
||||
logic.
|
||||
test-specific. TestManifest faciliates `skip-if` and `run-if` logic.
|
||||
|
||||
3. Optionally, a harness will have an integration layer than inherits
|
||||
from TestManifest if more harness-specific customization is desired at
|
||||
the manifest level.
|
||||
|
||||
See the source code at http://hg.mozilla.org/automation/ManifestDestiny
|
||||
See the source code at https://github.com/mozilla/mozbase/tree/master/manifestdestiny
|
||||
and
|
||||
http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestparser.py
|
||||
https://github.com/mozilla/mozbase/blob/master/manifestdestiny/manifestparser.py
|
||||
in particular.
|
||||
|
||||
|
||||
Using Manifests
|
||||
---------------
|
||||
# Using Manifests
|
||||
|
||||
A test harness will normally call ``TestManifest.active_tests`` (
|
||||
http://hg.mozilla.org/automation/ManifestDestiny/file/c0399fbfa830/manifestparser.py#l506 )::
|
||||
A test harness will normally call `TestManifest.active_tests`:
|
||||
|
||||
506 def active_tests(self, exists=True, disabled=True, **tags):
|
||||
def active_tests(self, exists=True, disabled=True, **tags):
|
||||
|
||||
The manifests are passed to the ``__init__`` or ``read`` methods with
|
||||
appropriate arguments. ``active_tests`` then allows you to select the
|
||||
tests you want::
|
||||
The manifests are passed to the `__init__` or `read` methods with
|
||||
appropriate arguments. `active_tests` then allows you to select the
|
||||
tests you want:
|
||||
|
||||
- exists : return only existing tests
|
||||
- disabled : whether to return disabled tests; if not these will be
|
||||
filtered out; if True (the default), the ``disabled`` key of a
|
||||
filtered out; if True (the default), the `disabled` key of a
|
||||
test's metadata will be present and will be set to the reason that a
|
||||
test is disabled
|
||||
- tags : keys and values to filter on (e.g. ``os='linux'``)
|
||||
- tags : keys and values to filter on (e.g. `os='linux'`)
|
||||
|
||||
``active_tests`` looks for tests with ``skip-if.${TAG}`` or
|
||||
``run-if``. If the condition is or is not fulfilled,
|
||||
`active_tests` looks for tests with `skip-if`
|
||||
`run-if`. If the condition is or is not fulfilled,
|
||||
respectively, the test is marked as disabled. For instance, if you
|
||||
pass ``**dict(os='linux')`` as ``**tags``, if a test contains a line
|
||||
``skip-if = os == 'linux'`` this test will be disabled, or
|
||||
``run-if = os = 'win'`` in which case the test will also be disabled. It
|
||||
is up to the harness to pass in tags appropriate to its usage.
|
||||
pass `**dict(os='linux')` as `**tags`, if a test contains a line
|
||||
`skip-if = os == 'linux'` this test will be disabled, or
|
||||
`run-if = os = 'win'` in which case the test will also be disabled. It
|
||||
is up to the harness to pass in tags appropriate to its usage.
|
||||
|
||||
|
||||
Creating Manifests
|
||||
------------------
|
||||
# Creating Manifests
|
||||
|
||||
ManifestDestiny comes with a console script, ``manifestparser create``, that
|
||||
ManifestDestiny comes with a console script, `manifestparser create`, that
|
||||
may be used to create a seed manifest structure from a directory of
|
||||
files. Run ``manifestparser help create`` for usage information.
|
||||
files. Run `manifestparser help create` for usage information.
|
||||
|
||||
|
||||
Copying Manifests
|
||||
-----------------
|
||||
# Copying Manifests
|
||||
|
||||
To copy tests and manifests from a source::
|
||||
To copy tests and manifests from a source:
|
||||
|
||||
manifestparser [options] copy from_manifest to_directory -tag1 -tag2 --key1=value1 key2=value2 ...
|
||||
manifestparser [options] copy from_manifest to_directory -tag1 -tag2 --key1=value1 key2=value2 ...
|
||||
|
||||
|
||||
Upating Tests
|
||||
-------------
|
||||
# Upating Tests
|
||||
|
||||
To update the tests associated with with a manifest from a source
|
||||
directory::
|
||||
directory:
|
||||
|
||||
manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
# Tests
|
||||
|
||||
ManifestDestiny includes a suite of tests:
|
||||
|
||||
http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestdestiny/tests
|
||||
https://github.com/mozilla/mozbase/tree/master/manifestdestiny/tests
|
||||
|
||||
``test_manifest.txt`` is a doctest that may be helpful in figuring out
|
||||
how to use the API. Tests are run via ``python test.py``.
|
||||
`test_manifest.txt` is a doctest that may be helpful in figuring out
|
||||
how to use the API. Tests are run via `python test.py`.
|
||||
|
||||
|
||||
Bugs
|
||||
----
|
||||
# Bugs
|
||||
|
||||
Please file any bugs or feature requests at
|
||||
Please file any bugs or feature requests at
|
||||
|
||||
https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=ManifestParser
|
||||
|
||||
Or contact jhammel @mozilla.org or in #ateam on irc.mozilla.org
|
||||
|
||||
|
||||
CLI
|
||||
---
|
||||
# CLI
|
||||
|
||||
Run ``manifestparser help`` for usage information.
|
||||
Run `manifestparser help` for usage information.
|
||||
|
||||
To create a manifest from a set of directories::
|
||||
To create a manifest from a set of directories:
|
||||
|
||||
manifestparser [options] create directory <directory> <...> [create-options]
|
||||
manifestparser [options] create directory <directory> <...> [create-options]
|
||||
|
||||
To output a manifest of tests::
|
||||
To output a manifest of tests:
|
||||
|
||||
manifestparser [options] write manifest <manifest> <...> -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
manifestparser [options] write manifest <manifest> <...> -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
|
||||
To copy tests and manifests from a source::
|
||||
To copy tests and manifests from a source:
|
||||
|
||||
manifestparser [options] copy from_manifest to_manifest -tag1 -tag2 --key1=value1 key2=value2 ...
|
||||
manifestparser [options] copy from_manifest to_manifest -tag1 -tag2 --key1=value1 key2=value2 ...
|
||||
|
||||
To update the tests associated with with a manifest from a source
|
||||
directory::
|
||||
directory:
|
||||
|
||||
manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
|
||||
|
||||
|
||||
Design Considerations
|
||||
---------------------
|
||||
# Design Considerations
|
||||
|
||||
Contrary to some opinion, manifestparser.py and the associated .ini
|
||||
format were not magically plucked from the sky but were descended upon
|
||||
@ -296,7 +277,7 @@ through several design considerations.
|
||||
* test manifests should be ordered. While python 2.6 and greater has
|
||||
a ConfigParser that can use an ordered dictionary, it is a
|
||||
requirement that we support python 2.4 for the build + testing
|
||||
environment. To that end, a ``read_ini`` function was implemented
|
||||
environment. To that end, a `read_ini` function was implemented
|
||||
in manifestparser.py that should be the equivalent of the .ini
|
||||
dialect used by ConfigParser.
|
||||
|
||||
@ -304,7 +285,7 @@ through several design considerations.
|
||||
there was initially some thought of using JSON, there was pushback
|
||||
that JSON was not easily editable. An ideal manifest format would
|
||||
degenerate to a line-separated list of files. While .ini format
|
||||
requires an additional ``[]`` per line, and while there have been
|
||||
requires an additional `[]` per line, and while there have been
|
||||
complaints about this, hopefully this is good enough.
|
||||
|
||||
* python does not have an in-built YAML parser. Since it was
|
||||
@ -322,14 +303,13 @@ through several design considerations.
|
||||
transported. Traditionally, test harnesses have lived in
|
||||
mozilla-central. This is less true these days and it is increasingly
|
||||
likely that more tests will not live in mozilla-central going
|
||||
forward. So ``manifestparser.py`` should be highly consumable. To
|
||||
forward. So `manifestparser.py` should be highly consumable. To
|
||||
this end, it is a single file, as appropriate to mozilla-central,
|
||||
which is also a working python package deployed to PyPI for easy
|
||||
installation.
|
||||
installation.
|
||||
|
||||
|
||||
Historical Reference
|
||||
--------------------
|
||||
# Historical Reference
|
||||
|
||||
Date-ordered list of links about how manifests came to be where they are today::
|
||||
|
38
testing/mozbase/manifestdestiny/manifestparser/__init__.py
Normal file
38
testing/mozbase/manifestdestiny/manifestparser/__init__.py
Normal file
@ -0,0 +1,38 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is manifestdestiny.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2010
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Jeff Hammel <jhammel@mozilla.com> (Original author)
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
from manifestparser import *
|
47
testing/mozbase/manifestdestiny/manifestparser.py → testing/mozbase/manifestdestiny/manifestparser/manifestparser.py
Normal file → Executable file
47
testing/mozbase/manifestdestiny/manifestparser.py → testing/mozbase/manifestdestiny/manifestparser/manifestparser.py
Normal file → Executable file
@ -368,7 +368,7 @@ def read_ini(fp, variables=None, default='DEFAULT',
|
||||
|
||||
if strict:
|
||||
# make sure this section doesn't already exist
|
||||
assert section not in section_names
|
||||
assert section not in section_names, "Section '%s' already found in '%s'" % (section, section_names)
|
||||
|
||||
section_names.add(section)
|
||||
current_section = {}
|
||||
@ -1005,49 +1005,6 @@ class HelpCLI(CLICommand):
|
||||
for command in sorted(commands):
|
||||
print ' %s : %s' % (command, commands[command].__doc__.strip())
|
||||
|
||||
class SetupCLI(CLICommand):
|
||||
"""
|
||||
setup using setuptools
|
||||
"""
|
||||
# use setup.py from the repo when you want to distribute to python!
|
||||
# otherwise setuptools will complain that it can't find setup.py
|
||||
# and result in a useless package
|
||||
|
||||
usage = '%prog [options] setup [setuptools options]'
|
||||
|
||||
def __call__(self, options, args):
|
||||
sys.argv = [sys.argv[0]] + args
|
||||
assert setup is not None, "You must have setuptools installed to use SetupCLI"
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
filename = os.path.join(here, 'README.txt')
|
||||
description = file(filename).read()
|
||||
except:
|
||||
description = ''
|
||||
os.chdir(here)
|
||||
|
||||
setup(name='ManifestDestiny',
|
||||
version=version,
|
||||
description="Universal manifests for Mozilla test harnesses",
|
||||
long_description=description,
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='mozilla manifests',
|
||||
author='Jeff Hammel',
|
||||
author_email='jhammel@mozilla.com',
|
||||
url='https://wiki.mozilla.org/Auto-tools/Projects/ManifestDestiny',
|
||||
license='MPL',
|
||||
zip_safe=False,
|
||||
py_modules=['manifestparser'],
|
||||
install_requires=[
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
manifestparser = manifestparser:main
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
class UpdateCLI(CLICommand):
|
||||
"""
|
||||
update the tests as listed in a manifest from a directory
|
||||
@ -1081,8 +1038,6 @@ commands = { 'create': CreateCLI,
|
||||
'help': HelpCLI,
|
||||
'update': UpdateCLI,
|
||||
'write': WriteCLI }
|
||||
if setup is not None:
|
||||
commands['setup'] = SetupCLI
|
||||
|
||||
def main(args=sys.argv[1:]):
|
||||
"""console_script entry point"""
|
@ -41,6 +41,37 @@
|
||||
# otherwise setuptools will complain that it can't find setup.py
|
||||
# and result in a useless package
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
import sys
|
||||
from manifestparser import SetupCLI
|
||||
SetupCLI(None)(None, sys.argv[1:])
|
||||
import os
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
filename = os.path.join(here, 'README.txt')
|
||||
description = file(filename).read()
|
||||
except:
|
||||
description = ''
|
||||
|
||||
PACKAGE_NAME = "ManifestDestiny"
|
||||
PACKAGE_VERSION = "0.5.4"
|
||||
|
||||
setup(name=PACKAGE_NAME,
|
||||
version=PACKAGE_VERSION,
|
||||
description="Universal manifests for Mozilla test harnesses",
|
||||
long_description=description,
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='mozilla manifests',
|
||||
author='Jeff Hammel',
|
||||
author_email='jhammel@mozilla.com',
|
||||
url='https://github.com/mozilla/mozbase/tree/master/manifestdestiny',
|
||||
license='MPL',
|
||||
zip_safe=False,
|
||||
packages=find_packages(exclude=['legacy']),
|
||||
install_requires=[
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
manifestparser = manifestparser:main
|
||||
""",
|
||||
)
|
||||
|
0
testing/mozbase/manifestdestiny/tests/test.py
Normal file → Executable file
0
testing/mozbase/manifestdestiny/tests/test.py
Normal file → Executable file
5
testing/mozbase/mozdevice/README.md
Normal file
5
testing/mozbase/mozdevice/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
[mozdevice](https://github.com/mozilla/mozbase/tree/master/mozdevice) provides
|
||||
an interface to interact with a remote device such as an Android phone connected
|
||||
to a workstation. Currently there are two implementations of the interface: one
|
||||
uses a TCP-based protocol to communicate with a server running on the device,
|
||||
another uses Android's adb utility.
|
39
testing/mozbase/mozdevice/mozdevice/__init__.py
Normal file
39
testing/mozbase/mozdevice/mozdevice/__init__.py
Normal file
@ -0,0 +1,39 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Mozbase.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Will Lachance <wlachance@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
from devicemanagerADB import DeviceManagerADB
|
||||
from devicemanagerSUT import DeviceManagerSUT
|
538
testing/mozbase/mozdevice/mozdevice/devicemanager.py
Normal file
538
testing/mozbase/mozdevice/mozdevice/devicemanager.py
Normal file
@ -0,0 +1,538 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Test Automation Framework.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Joel Maher.
|
||||
#
|
||||
# Portions created by the Initial Developer are Copyright (C) 2009
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Joel Maher <joel.maher@gmail.com> (Original Developer)
|
||||
# Clint Talbert <cmtalbert@gmail.com>
|
||||
# Mark Cote <mcote@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
import socket
|
||||
import os
|
||||
import re
|
||||
|
||||
class FileError(Exception):
|
||||
" Signifies an error which occurs while doing a file operation."
|
||||
|
||||
def __init__(self, msg = ''):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class DMError(Exception):
|
||||
"generic devicemanager exception."
|
||||
|
||||
def __init__(self, msg= ''):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
class DeviceManager:
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def pushFile(self, localname, destname):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: directory name
|
||||
# failure: None
|
||||
def mkDir(self, name):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# make directory structure on the device
|
||||
# external function
|
||||
# returns:
|
||||
# success: directory structure that we created
|
||||
# failure: None
|
||||
def mkDirs(self, filename):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# push localDir from host to remoteDir on the device
|
||||
# external function
|
||||
# returns:
|
||||
# success: remoteDir
|
||||
# failure: None
|
||||
def pushDir(self, localDir, remoteDir):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def dirExists(self, dirname):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# Because we always have / style paths we make this a lot easier with some
|
||||
# assumptions
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def fileExists(self, filepath):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# list files on the device, requires cd to directory first
|
||||
# external function
|
||||
# returns:
|
||||
# success: array of filenames, ['file1', 'file2', ...]
|
||||
# failure: []
|
||||
def listFiles(self, rootdir):
|
||||
assert 0 == 1
|
||||
return []
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
|
||||
# failure: None
|
||||
def removeFile(self, filename):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# does a recursive delete of directory on the device: rm -Rf remoteDir
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
|
||||
# failure: None
|
||||
def removeDir(self, remoteDir):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: array of process tuples
|
||||
# failure: []
|
||||
def getProcessList(self):
|
||||
assert 0 == 1
|
||||
return []
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: pid
|
||||
# failure: None
|
||||
def fireProcess(self, appname, failIfRunning=False):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output filename
|
||||
# failure: None
|
||||
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# loops until 'process' has exited or 'timeout' seconds is reached
|
||||
# loop sleeps for 'interval' seconds between iterations
|
||||
# external function
|
||||
# returns:
|
||||
# success: [file contents, None]
|
||||
# failure: [None, None]
|
||||
def communicate(self, process, timeout = 600, interval = 5):
|
||||
timed_out = True
|
||||
if (timeout > 0):
|
||||
total_time = 0
|
||||
while total_time < timeout:
|
||||
time.sleep(interval)
|
||||
if self.processExist(process) == None:
|
||||
timed_out = False
|
||||
break
|
||||
total_time += interval
|
||||
|
||||
if (timed_out == True):
|
||||
return [None, None]
|
||||
|
||||
return [self.getFile(process, "temp.txt"), None]
|
||||
|
||||
# iterates process list and returns pid if exists, otherwise None
|
||||
# external function
|
||||
# returns:
|
||||
# success: pid
|
||||
# failure: None
|
||||
def processExist(self, appname):
|
||||
pid = None
|
||||
|
||||
#filter out extra spaces
|
||||
parts = filter(lambda x: x != '', appname.split(' '))
|
||||
appname = ' '.join(parts)
|
||||
|
||||
#filter out the quoted env string if it exists
|
||||
#ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
|
||||
parts = appname.split('"')
|
||||
if (len(parts) > 2):
|
||||
appname = ' '.join(parts[2:]).strip()
|
||||
|
||||
pieces = appname.split(' ')
|
||||
parts = pieces[0].split('/')
|
||||
app = parts[-1]
|
||||
procre = re.compile('.*' + app + '.*')
|
||||
|
||||
procList = self.getProcessList()
|
||||
if (procList == []):
|
||||
return None
|
||||
|
||||
for proc in procList:
|
||||
if (procre.match(proc[1])):
|
||||
pid = proc[0]
|
||||
break
|
||||
return pid
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output from testagent
|
||||
# failure: None
|
||||
def killProcess(self, appname):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: filecontents
|
||||
# failure: None
|
||||
def catFile(self, remoteFile):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of pullfile, string
|
||||
# failure: None
|
||||
def pullFile(self, remoteFile):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# copy file from device (remoteFile) to host (localFile)
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of pullfile, string
|
||||
# failure: None
|
||||
def getFile(self, remoteFile, localFile = ''):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# copy directory structure from device (remoteDir) to host (localDir)
|
||||
# external function
|
||||
# checkDir exists so that we don't create local directories if the
|
||||
# remote directory doesn't exist but also so that we don't call isDir
|
||||
# twice when recursing.
|
||||
# returns:
|
||||
# success: list of files, string
|
||||
# failure: None
|
||||
def getDirectory(self, remoteDir, localDir, checkDir=True):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
# Throws a FileError exception when null (invalid dir/filename)
|
||||
def isDir(self, remotePath):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# true/false check if the two files have the same md5 sum
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def validateFile(self, remoteFile, localFile):
|
||||
assert 0 == 1
|
||||
return False
|
||||
|
||||
# return the md5 sum of a remote file
|
||||
# internal function
|
||||
# returns:
|
||||
# success: MD5 hash for given filename
|
||||
# failure: None
|
||||
def getRemoteHash(self, filename):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# return the md5 sum of a file on the host
|
||||
# internal function
|
||||
# returns:
|
||||
# success: MD5 hash for given filename
|
||||
# failure: None
|
||||
def getLocalHash(self, filename):
|
||||
file = open(filename, 'rb')
|
||||
if (file == None):
|
||||
return None
|
||||
|
||||
try:
|
||||
mdsum = hashlib.md5()
|
||||
except:
|
||||
return None
|
||||
|
||||
while 1:
|
||||
data = file.read(1024)
|
||||
if not data:
|
||||
break
|
||||
mdsum.update(data)
|
||||
|
||||
file.close()
|
||||
hexval = mdsum.hexdigest()
|
||||
if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
|
||||
return hexval
|
||||
# Gets the device root for the testing area on the device
|
||||
# For all devices we will use / type slashes and depend on the device-agent
|
||||
# to sort those out. The agent will return us the device location where we
|
||||
# should store things, we will then create our /tests structure relative to
|
||||
# that returned path.
|
||||
# Structure on the device is as follows:
|
||||
# /tests
|
||||
# /<fennec>|<firefox> --> approot
|
||||
# /profile
|
||||
# /xpcshell
|
||||
# /reftest
|
||||
# /mochitest
|
||||
#
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for device root
|
||||
# failure: None
|
||||
def getDeviceRoot(self):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# Either we will have /tests/fennec or /tests/firefox but we will never have
|
||||
# both. Return the one that exists
|
||||
# TODO: ensure we can support org.mozilla.firefox
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for app root
|
||||
# failure: None
|
||||
def getAppRoot(self):
|
||||
devroot = self.getDeviceRoot()
|
||||
if (devroot == None):
|
||||
return None
|
||||
|
||||
if (self.dirExists(devroot + '/fennec')):
|
||||
return devroot + '/fennec'
|
||||
elif (self.dirExists(devroot + '/firefox')):
|
||||
return devroot + '/firefox'
|
||||
elif (self.dirExsts('/data/data/org.mozilla.fennec')):
|
||||
return 'org.mozilla.fennec'
|
||||
elif (self.dirExists('/data/data/org.mozilla.firefox')):
|
||||
return 'org.mozilla.firefox'
|
||||
elif (self.dirExists('/data/data/org.mozilla.fennec_aurora')):
|
||||
return 'org.mozilla.fennec_aurora'
|
||||
elif (self.dirExists('/data/data/org.mozilla.firefox_beta')):
|
||||
return 'org.mozilla.firefox_beta'
|
||||
|
||||
# Failure (either not installed or not a recognized platform)
|
||||
return None
|
||||
|
||||
# Gets the directory location on the device for a specific test type
|
||||
# Type is one of: xpcshell|reftest|mochitest
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for test root
|
||||
# failure: None
|
||||
def getTestRoot(self, type):
|
||||
devroot = self.getDeviceRoot()
|
||||
if (devroot == None):
|
||||
return None
|
||||
|
||||
if (re.search('xpcshell', type, re.I)):
|
||||
self.testRoot = devroot + '/xpcshell'
|
||||
elif (re.search('?(i)reftest', type)):
|
||||
self.testRoot = devroot + '/reftest'
|
||||
elif (re.search('?(i)mochitest', type)):
|
||||
self.testRoot = devroot + '/mochitest'
|
||||
return self.testRoot
|
||||
|
||||
# Sends a specific process ID a signal code and action.
|
||||
# For Example: SIGINT and SIGDFL to process x
|
||||
def signal(self, processID, signalType, signalAction):
|
||||
# currently not implemented in device agent - todo
|
||||
pass
|
||||
|
||||
# Get a return code from process ending -- needs support on device-agent
|
||||
def getReturnCode(self, processID):
|
||||
# TODO: make this real
|
||||
return 0
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of unzip command
|
||||
# failure: None
|
||||
def unpackFile(self, filename):
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: status from test agent
|
||||
# failure: None
|
||||
def reboot(self, ipAddr=None, port=30000):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# validate localDir from host to remoteDir on the device
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def validateDir(self, localDir, remoteDir):
|
||||
if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
|
||||
for root, dirs, files in os.walk(localDir):
|
||||
parts = root.split(localDir)
|
||||
for file in files:
|
||||
remoteRoot = remoteDir + '/' + parts[1]
|
||||
remoteRoot = remoteRoot.replace('/', '/')
|
||||
if (parts[1] == ""): remoteRoot = remoteDir
|
||||
remoteName = remoteRoot + '/' + file
|
||||
if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
|
||||
return False
|
||||
return True
|
||||
|
||||
# Returns information about the device:
|
||||
# Directive indicates the information you want to get, your choices are:
|
||||
# os - name of the os
|
||||
# id - unique id of the device
|
||||
# uptime - uptime of the device
|
||||
# systime - system time of the device
|
||||
# screen - screen resolution
|
||||
# memory - memory stats
|
||||
# process - list of running processes (same as ps)
|
||||
# disk - total, free, available bytes on disk
|
||||
# power - power status (charge, battery temp)
|
||||
# all - all of them - or call it with no parameters to get all the information
|
||||
# returns:
|
||||
# success: dict of info strings by directive name
|
||||
# failure: {}
|
||||
def getInfo(self, directive=None):
|
||||
assert 0 == 1
|
||||
return {}
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output from agent for inst command
|
||||
# failure: None
|
||||
def installApp(self, appBundlePath, destPath=None):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: None
|
||||
def uninstallAppAndReboot(self, appName, installPath=None):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: text status from command or callback server
|
||||
# failure: None
|
||||
def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: time in ms
|
||||
# failure: None
|
||||
def getCurrentTime(self):
|
||||
assert 0 == 1
|
||||
return None
|
||||
|
||||
|
||||
class NetworkTools:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# Utilities to get the local ip address
|
||||
def getInterfaceIp(self, ifname):
|
||||
if os.name != "nt":
|
||||
import fcntl
|
||||
import struct
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8915, # SIOCGIFADDR
|
||||
struct.pack('256s', ifname[:15])
|
||||
)[20:24])
|
||||
else:
|
||||
return None
|
||||
|
||||
def getLanIp(self):
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
if ip.startswith("127.") and os.name != "nt":
|
||||
interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
|
||||
for ifname in interfaces:
|
||||
try:
|
||||
ip = self.getInterfaceIp(ifname)
|
||||
break;
|
||||
except IOError:
|
||||
pass
|
||||
return ip
|
||||
|
||||
# Gets an open port starting with the seed by incrementing by 1 each time
|
||||
def findOpenPort(self, ip, seed):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
connected = False
|
||||
if isinstance(seed, basestring):
|
||||
seed = int(seed)
|
||||
maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
|
||||
while not connected:
|
||||
try:
|
||||
s.bind((ip, seed))
|
||||
connected = True
|
||||
s.close()
|
||||
break
|
||||
except:
|
||||
if seed > maxportnum:
|
||||
print "Could not find open port after checking 5000 ports"
|
||||
raise
|
||||
seed += 1
|
||||
except:
|
||||
print "Socket error trying to find open port"
|
||||
|
||||
return seed
|
||||
|
580
testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
Normal file
580
testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
Normal file
@ -0,0 +1,580 @@
|
||||
import subprocess
|
||||
from devicemanager import DeviceManager, DMError
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
class DeviceManagerADB(DeviceManager):
|
||||
|
||||
def __init__(self, host = None, port = 20701, retrylimit = 5, packageName = None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.retrylimit = retrylimit
|
||||
self.retries = 0
|
||||
self._sock = None
|
||||
self.useRunAs = False
|
||||
self.packageName = None
|
||||
if packageName == None:
|
||||
if os.getenv('USER'):
|
||||
packageName = 'org.mozilla.fennec_' + os.getenv('USER')
|
||||
else:
|
||||
packageName = 'org.mozilla.fennec_'
|
||||
self.Init(packageName)
|
||||
|
||||
def Init(self, packageName):
|
||||
# Initialization code that may fail: Catch exceptions here to allow
|
||||
# successful initialization even if, for example, adb is not installed.
|
||||
try:
|
||||
self.verifyADB()
|
||||
self.verifyRunAs(packageName)
|
||||
except:
|
||||
self.useRunAs = False
|
||||
self.packageName = None
|
||||
try:
|
||||
# a test to see if we have root privs
|
||||
files = self.listFiles("/data/data")
|
||||
if (len(files) == 1):
|
||||
if (files[0].find("Permission denied") != -1):
|
||||
print "NOT running as root"
|
||||
raise Exception("not running as root")
|
||||
except:
|
||||
try:
|
||||
self.checkCmd(["root"])
|
||||
except:
|
||||
print "restarting as root failed"
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def pushFile(self, localname, destname):
|
||||
try:
|
||||
if (os.name == "nt"):
|
||||
destname = destname.replace('\\', '/')
|
||||
if (self.useRunAs):
|
||||
remoteTmpFile = self.tmpDir + "/" + os.path.basename(localname)
|
||||
self.checkCmd(["push", os.path.realpath(localname), remoteTmpFile])
|
||||
self.checkCmdAs(["shell", "cp", remoteTmpFile, destname])
|
||||
self.checkCmd(["shell", "rm", remoteTmpFile])
|
||||
else:
|
||||
self.checkCmd(["push", os.path.realpath(localname), destname])
|
||||
if (self.isDir(destname)):
|
||||
destname = destname + "/" + os.path.basename(localname)
|
||||
self.chmodDir(destname)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: directory name
|
||||
# failure: None
|
||||
def mkDir(self, name):
|
||||
try:
|
||||
self.checkCmdAs(["shell", "mkdir", name])
|
||||
self.chmodDir(name)
|
||||
return name
|
||||
except:
|
||||
return None
|
||||
|
||||
# make directory structure on the device
|
||||
# external function
|
||||
# returns:
|
||||
# success: directory structure that we created
|
||||
# failure: None
|
||||
def mkDirs(self, filename):
|
||||
parts = filename.split('/')
|
||||
name = ""
|
||||
for part in parts:
|
||||
if (part == parts[-1]): break
|
||||
if (part != ""):
|
||||
name += '/' + part
|
||||
if (not self.dirExists(name)):
|
||||
if (self.mkDir(name) == None):
|
||||
print "failed making directory: " + str(name)
|
||||
return None
|
||||
return name
|
||||
|
||||
# push localDir from host to remoteDir on the device
|
||||
# external function
|
||||
# returns:
|
||||
# success: remoteDir
|
||||
# failure: None
|
||||
def pushDir(self, localDir, remoteDir):
|
||||
# adb "push" accepts a directory as an argument, but if the directory
|
||||
# contains symbolic links, the links are pushed, rather than the linked
|
||||
# files; we push file-by-file to get around this limitation
|
||||
try:
|
||||
if (not self.dirExists(remoteDir)):
|
||||
self.mkDirs(remoteDir+"/x")
|
||||
for root, dirs, files in os.walk(localDir, followlinks='true'):
|
||||
relRoot = os.path.relpath(root, localDir)
|
||||
for file in files:
|
||||
localFile = os.path.join(root, file)
|
||||
remoteFile = remoteDir + "/"
|
||||
if (relRoot!="."):
|
||||
remoteFile = remoteFile + relRoot + "/"
|
||||
remoteFile = remoteFile + file
|
||||
self.pushFile(localFile, remoteFile)
|
||||
for dir in dirs:
|
||||
targetDir = remoteDir + "/"
|
||||
if (relRoot!="."):
|
||||
targetDir = targetDir + relRoot + "/"
|
||||
targetDir = targetDir + dir
|
||||
if (not self.dirExists(targetDir)):
|
||||
self.mkDir(targetDir)
|
||||
self.checkCmdAs(["shell", "chmod", "777", remoteDir])
|
||||
return True
|
||||
except:
|
||||
print "pushing " + localDir + " to " + remoteDir + " failed"
|
||||
return False
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def dirExists(self, dirname):
|
||||
return self.isDir(dirname)
|
||||
|
||||
# Because we always have / style paths we make this a lot easier with some
|
||||
# assumptions
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def fileExists(self, filepath):
|
||||
p = self.runCmd(["shell", "ls", "-a", filepath])
|
||||
data = p.stdout.readlines()
|
||||
if (len(data) == 1):
|
||||
if (data[0].rstrip() == filepath):
|
||||
return True
|
||||
return False
|
||||
|
||||
def removeFile(self, filename):
|
||||
return self.runCmd(["shell", "rm", filename]).stdout.read()
|
||||
|
||||
# does a recursive delete of directory on the device: rm -Rf remoteDir
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
|
||||
# failure: None
|
||||
def removeSingleDir(self, remoteDir):
|
||||
return self.runCmd(["shell", "rmdir", remoteDir]).stdout.read()
|
||||
|
||||
# does a recursive delete of directory on the device: rm -Rf remoteDir
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
|
||||
# failure: None
|
||||
def removeDir(self, remoteDir):
|
||||
out = ""
|
||||
if (self.isDir(remoteDir)):
|
||||
files = self.listFiles(remoteDir.strip())
|
||||
for f in files:
|
||||
if (self.isDir(remoteDir.strip() + "/" + f.strip())):
|
||||
out += self.removeDir(remoteDir.strip() + "/" + f.strip())
|
||||
else:
|
||||
out += self.removeFile(remoteDir.strip() + "/" + f.strip())
|
||||
out += self.removeSingleDir(remoteDir.strip())
|
||||
else:
|
||||
out += self.removeFile(remoteDir.strip())
|
||||
return out
|
||||
|
||||
def isDir(self, remotePath):
|
||||
p = self.runCmd(["shell", "ls", "-a", remotePath])
|
||||
data = p.stdout.readlines()
|
||||
if (len(data) == 0):
|
||||
return True
|
||||
if (len(data) == 1):
|
||||
if (data[0].rstrip() == remotePath):
|
||||
return False
|
||||
if (data[0].find("No such file or directory") != -1):
|
||||
return False
|
||||
if (data[0].find("Not a directory") != -1):
|
||||
return False
|
||||
return True
|
||||
|
||||
def listFiles(self, rootdir):
|
||||
p = self.runCmd(["shell", "ls", "-a", rootdir])
|
||||
data = p.stdout.readlines()
|
||||
if (len(data) == 1):
|
||||
if (data[0] == rootdir):
|
||||
return []
|
||||
if (data[0].find("No such file or directory") != -1):
|
||||
return []
|
||||
if (data[0].find("Not a directory") != -1):
|
||||
return []
|
||||
return data
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: array of process tuples
|
||||
# failure: []
|
||||
def getProcessList(self):
|
||||
p = self.runCmd(["shell", "ps"])
|
||||
# first line is the headers
|
||||
p.stdout.readline()
|
||||
proc = p.stdout.readline()
|
||||
ret = []
|
||||
while (proc):
|
||||
els = proc.split()
|
||||
ret.append(list([els[1], els[len(els) - 1], els[0]]))
|
||||
proc = p.stdout.readline()
|
||||
return ret
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: pid
|
||||
# failure: None
|
||||
def fireProcess(self, appname, failIfRunning=False):
|
||||
#strip out env vars
|
||||
parts = appname.split('"');
|
||||
if (len(parts) > 2):
|
||||
parts = parts[2:]
|
||||
return self.launchProcess(parts, failIfRunning)
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output filename
|
||||
# failure: None
|
||||
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
|
||||
acmd = ["shell", "am","start"]
|
||||
cmd = ' '.join(cmd).strip()
|
||||
i = cmd.find(" ")
|
||||
acmd.append("-n")
|
||||
acmd.append(cmd[0:i] + "/.App")
|
||||
acmd.append("--es")
|
||||
acmd.append("args")
|
||||
acmd.append(cmd[i:])
|
||||
print acmd
|
||||
self.checkCmd(acmd)
|
||||
return outputFile;
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output from testagent
|
||||
# failure: None
|
||||
def killProcess(self, appname):
|
||||
procs = self.getProcessList()
|
||||
for (pid, name, user) in procs:
|
||||
if name == appname:
|
||||
p = self.runCmdAs(["shell", "kill", pid])
|
||||
return p.stdout.read()
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: filecontents
|
||||
# failure: None
|
||||
def catFile(self, remoteFile):
|
||||
#p = self.runCmd(["shell", "cat", remoteFile])
|
||||
#return p.stdout.read()
|
||||
return self.getFile(remoteFile)
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of pullfile, string
|
||||
# failure: None
|
||||
def pullFile(self, remoteFile):
|
||||
#return self.catFile(remoteFile)
|
||||
return self.getFile(remoteFile)
|
||||
|
||||
# copy file from device (remoteFile) to host (localFile)
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of pullfile, string
|
||||
# failure: None
|
||||
def getFile(self, remoteFile, localFile = 'tmpfile_dm_adb'):
|
||||
# TODO: add debug flags and allow for printing stdout
|
||||
# self.runCmd(["pull", remoteFile, localFile])
|
||||
try:
|
||||
self.runCmd(["pull", remoteFile, localFile]).stdout.read()
|
||||
f = open(localFile)
|
||||
ret = f.read()
|
||||
f.close()
|
||||
return ret;
|
||||
except:
|
||||
return None
|
||||
|
||||
# copy directory structure from device (remoteDir) to host (localDir)
|
||||
# external function
|
||||
# checkDir exists so that we don't create local directories if the
|
||||
# remote directory doesn't exist but also so that we don't call isDir
|
||||
# twice when recursing.
|
||||
# returns:
|
||||
# success: list of files, string
|
||||
# failure: None
|
||||
def getDirectory(self, remoteDir, localDir, checkDir=True):
|
||||
ret = []
|
||||
p = self.runCmd(["pull", remoteDir, localDir])
|
||||
p.stderr.readline()
|
||||
line = p.stderr.readline()
|
||||
while (line):
|
||||
els = line.split()
|
||||
f = els[len(els) - 1]
|
||||
i = f.find(localDir)
|
||||
if (i != -1):
|
||||
if (localDir[len(localDir) - 1] != '/'):
|
||||
i = i + 1
|
||||
f = f[i + len(localDir):]
|
||||
i = f.find("/")
|
||||
if (i > 0):
|
||||
f = f[0:i]
|
||||
ret.append(f)
|
||||
line = p.stderr.readline()
|
||||
#the last line is a summary
|
||||
if (len(ret) > 0):
|
||||
ret.pop()
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
# true/false check if the two files have the same md5 sum
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
# failure: False
|
||||
def validateFile(self, remoteFile, localFile):
|
||||
return self.getRemoteHash(remoteFile) == self.getLocalHash(localFile)
|
||||
|
||||
# return the md5 sum of a remote file
|
||||
# internal function
|
||||
# returns:
|
||||
# success: MD5 hash for given filename
|
||||
# failure: None
|
||||
def getRemoteHash(self, filename):
|
||||
data = p = self.runCmd(["shell", "ls", "-l", filename]).stdout.read()
|
||||
return data.split()[3]
|
||||
|
||||
def getLocalHash(self, filename):
|
||||
data = p = subprocess.Popen(["ls", "-l", filename], stdout=subprocess.PIPE).stdout.read()
|
||||
return data.split()[4]
|
||||
|
||||
# Gets the device root for the testing area on the device
|
||||
# For all devices we will use / type slashes and depend on the device-agent
|
||||
# to sort those out. The agent will return us the device location where we
|
||||
# should store things, we will then create our /tests structure relative to
|
||||
# that returned path.
|
||||
# Structure on the device is as follows:
|
||||
# /tests
|
||||
# /<fennec>|<firefox> --> approot
|
||||
# /profile
|
||||
# /xpcshell
|
||||
# /reftest
|
||||
# /mochitest
|
||||
#
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for device root
|
||||
# failure: None
|
||||
def getDeviceRoot(self):
|
||||
# /mnt/sdcard/tests is preferred to /data/local/tests, but this can be
|
||||
# over-ridden by creating /data/local/tests
|
||||
testRoot = "/data/local/tests"
|
||||
if (self.dirExists(testRoot)):
|
||||
return testRoot
|
||||
root = "/mnt/sdcard"
|
||||
if (not self.dirExists(root)):
|
||||
root = "/data/local"
|
||||
testRoot = root + "/tests"
|
||||
if (not self.dirExists(testRoot)):
|
||||
self.mkDir(testRoot)
|
||||
return testRoot
|
||||
|
||||
# Either we will have /tests/fennec or /tests/firefox but we will never have
|
||||
# both. Return the one that exists
|
||||
# TODO: ensure we can support org.mozilla.firefox
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for app root
|
||||
# failure: None
|
||||
def getAppRoot(self):
|
||||
devroot = self.getDeviceRoot()
|
||||
if (devroot == None):
|
||||
return None
|
||||
|
||||
if (self.dirExists(devroot + '/fennec')):
|
||||
return devroot + '/fennec'
|
||||
elif (self.dirExists(devroot + '/firefox')):
|
||||
return devroot + '/firefox'
|
||||
elif (self.packageName and self.dirExists('/data/data/' + self.packageName)):
|
||||
return '/data/data/' + self.packageName
|
||||
|
||||
# Failure (either not installed or not a recognized platform)
|
||||
print "devicemanagerADB: getAppRoot failed"
|
||||
return None
|
||||
|
||||
# Gets the directory location on the device for a specific test type
|
||||
# Type is one of: xpcshell|reftest|mochitest
|
||||
# external function
|
||||
# returns:
|
||||
# success: path for test root
|
||||
# failure: None
|
||||
def getTestRoot(self, type):
|
||||
devroot = self.getDeviceRoot()
|
||||
if (devroot == None):
|
||||
return None
|
||||
|
||||
if (re.search('xpcshell', type, re.I)):
|
||||
self.testRoot = devroot + '/xpcshell'
|
||||
elif (re.search('?(i)reftest', type)):
|
||||
self.testRoot = devroot + '/reftest'
|
||||
elif (re.search('?(i)mochitest', type)):
|
||||
self.testRoot = devroot + '/mochitest'
|
||||
return self.testRoot
|
||||
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: status from test agent
|
||||
# failure: None
|
||||
def reboot(self, wait = False):
|
||||
ret = self.runCmd(["reboot"]).stdout.read()
|
||||
if (not wait):
|
||||
return "Success"
|
||||
countdown = 40
|
||||
while (countdown > 0):
|
||||
countdown
|
||||
try:
|
||||
self.checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
|
||||
return ret
|
||||
except:
|
||||
try:
|
||||
self.checkCmd(["root"])
|
||||
except:
|
||||
time.sleep(1)
|
||||
print "couldn't get root"
|
||||
return "Success"
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: text status from command or callback server
|
||||
# failure: None
|
||||
def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
|
||||
return self.runCmd(["install", "-r", appBundlePath]).stdout.read()
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: time in ms
|
||||
# failure: None
|
||||
def getCurrentTime(self):
|
||||
timestr = self.runCmd(["shell", "date", "+%s"]).stdout.read().strip()
|
||||
if (not timestr or not timestr.isdigit()):
|
||||
return None
|
||||
return str(int(timestr)*1000)
|
||||
|
||||
# Returns information about the device:
|
||||
# Directive indicates the information you want to get, your choices are:
|
||||
# os - name of the os
|
||||
# id - unique id of the device
|
||||
# uptime - uptime of the device
|
||||
# systime - system time of the device
|
||||
# screen - screen resolution
|
||||
# memory - memory stats
|
||||
# process - list of running processes (same as ps)
|
||||
# disk - total, free, available bytes on disk
|
||||
# power - power status (charge, battery temp)
|
||||
# all - all of them - or call it with no parameters to get all the information
|
||||
# returns:
|
||||
# success: dict of info strings by directive name
|
||||
# failure: {}
|
||||
def getInfo(self, directive="all"):
|
||||
ret = {}
|
||||
if (directive == "id" or directive == "all"):
|
||||
ret["id"] = self.runCmd(["get-serialno"]).stdout.read()
|
||||
if (directive == "os" or directive == "all"):
|
||||
ret["os"] = self.runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read()
|
||||
if (directive == "uptime" or directive == "all"):
|
||||
utime = self.runCmd(["shell", "uptime"]).stdout.read()
|
||||
if (not utime):
|
||||
raise DMError("error getting uptime")
|
||||
utime = utime[9:]
|
||||
hours = utime[0:utime.find(":")]
|
||||
utime = utime[utime[1:].find(":") + 2:]
|
||||
minutes = utime[0:utime.find(":")]
|
||||
utime = utime[utime[1:].find(":") + 2:]
|
||||
seconds = utime[0:utime.find(",")]
|
||||
ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"]
|
||||
if (directive == "process" or directive == "all"):
|
||||
ret["process"] = self.runCmd(["shell", "ps"]).stdout.read()
|
||||
if (directive == "systime" or directive == "all"):
|
||||
ret["systime"] = self.runCmd(["shell", "date"]).stdout.read()
|
||||
print ret
|
||||
return ret
|
||||
|
||||
def runCmd(self, args):
|
||||
args.insert(0, "adb")
|
||||
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
def runCmdAs(self, args):
|
||||
if self.useRunAs:
|
||||
args.insert(1, "run-as")
|
||||
args.insert(2, self.packageName)
|
||||
return self.runCmd(args)
|
||||
|
||||
def checkCmd(self, args):
|
||||
args.insert(0, "adb")
|
||||
return subprocess.check_call(args)
|
||||
|
||||
def checkCmdAs(self, args):
|
||||
if (self.useRunAs):
|
||||
args.insert(1, "run-as")
|
||||
args.insert(2, self.packageName)
|
||||
return self.checkCmd(args)
|
||||
|
||||
def chmodDir(self, remoteDir):
|
||||
if (self.isDir(remoteDir)):
|
||||
files = self.listFiles(remoteDir.strip())
|
||||
for f in files:
|
||||
if (self.isDir(remoteDir.strip() + "/" + f.strip())):
|
||||
self.chmodDir(remoteDir.strip() + "/" + f.strip())
|
||||
else:
|
||||
self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()])
|
||||
print "chmod " + remoteDir.strip()
|
||||
self.checkCmdAs(["shell", "chmod", "777", remoteDir])
|
||||
print "chmod " + remoteDir
|
||||
else:
|
||||
self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()])
|
||||
print "chmod " + remoteDir.strip()
|
||||
|
||||
def verifyADB(self):
|
||||
# Check to see if adb itself can be executed.
|
||||
try:
|
||||
self.runCmd(["version"])
|
||||
except Exception as (ex):
|
||||
print "unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH"
|
||||
|
||||
def isCpAvailable(self):
|
||||
# Some Android systems may not have a cp command installed,
|
||||
# or it may not be executable by the user.
|
||||
data = self.runCmd(["shell", "cp"]).stdout.read()
|
||||
if (re.search('Usage', data)):
|
||||
return True
|
||||
else:
|
||||
print "unable to execute 'cp' on device; consider installing busybox from Android Market"
|
||||
return False
|
||||
|
||||
def verifyRunAs(self, packageName):
|
||||
# If a valid package name is available, and certain other
|
||||
# conditions are met, devicemanagerADB can execute file operations
|
||||
# via the "run-as" command, so that pushed files and directories
|
||||
# are created by the uid associated with the package, more closely
|
||||
# echoing conditions encountered by Fennec at run time.
|
||||
# Check to see if run-as can be used here, by verifying a
|
||||
# file copy via run-as.
|
||||
self.useRunAs = False
|
||||
devroot = self.getDeviceRoot()
|
||||
if (packageName and self.isCpAvailable() and devroot):
|
||||
self.tmpDir = devroot + "/tmp"
|
||||
if (not self.dirExists(self.tmpDir)):
|
||||
self.mkDir(self.tmpDir)
|
||||
self.checkCmd(["shell", "run-as", packageName, "mkdir", devroot + "/sanity"])
|
||||
self.checkCmd(["push", os.path.abspath(sys.argv[0]), self.tmpDir + "/tmpfile"])
|
||||
self.checkCmd(["shell", "run-as", packageName, "cp", self.tmpDir + "/tmpfile", devroot + "/sanity"])
|
||||
if (self.fileExists(devroot + "/sanity/tmpfile")):
|
||||
print "will execute commands via run-as " + packageName
|
||||
self.packageName = packageName
|
||||
self.useRunAs = True
|
||||
self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
|
||||
self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
|
||||
|
1239
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
Normal file
1239
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
Normal file
File diff suppressed because it is too large
Load Diff
67
testing/mozbase/mozdevice/setup.py
Normal file
67
testing/mozbase/mozdevice/setup.py
Normal file
@ -0,0 +1,67 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozdevice.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Will Lachance <wlachance@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.1'
|
||||
|
||||
# take description from README
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
description = file(os.path.join(here, 'README.md')).read()
|
||||
except (OSError, IOError):
|
||||
description = ''
|
||||
|
||||
setup(name='mozdevice',
|
||||
version=version,
|
||||
description="Mozilla-authored device management",
|
||||
long_description=description,
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='',
|
||||
author='Mozilla Automation and Testing Team',
|
||||
author_email='tools@lists.mozilla.com',
|
||||
url='http://github.com/mozilla/mozbase',
|
||||
license='MPL',
|
||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[],
|
||||
entry_points="""
|
||||
# -*- Entry points: -*-
|
||||
""",
|
||||
)
|
39
testing/mozbase/mozhttpd/mozhttpd/__init__.py
Normal file
39
testing/mozbase/mozhttpd/mozhttpd/__init__.py
Normal file
@ -0,0 +1,39 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# the Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# William Lachance <wlachance@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
from mozhttpd import MozHttpd, MozRequestHandler
|
||||
import iface
|
62
testing/mozbase/mozhttpd/mozhttpd/iface.py
Normal file
62
testing/mozbase/mozhttpd/mozhttpd/iface.py
Normal file
@ -0,0 +1,62 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# the Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Joel Maher <joel.maher@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import os
|
||||
import socket
|
||||
if os.name != 'nt':
|
||||
import fcntl
|
||||
import struct
|
||||
|
||||
def _get_interface_ip(ifname):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8915, # SIOCGIFADDR
|
||||
struct.pack('256s', ifname[:15])
|
||||
)[20:24])
|
||||
|
||||
def get_lan_ip():
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
if ip.startswith("127.") and os.name != "nt":
|
||||
interfaces = ["eth0", "eth1", "eth2", "wlan0", "wlan1", "wifi0", "ath0", "ath1", "ppp0"]
|
||||
for ifname in interfaces:
|
||||
try:
|
||||
ip = _get_interface_ip(ifname)
|
||||
break;
|
||||
except IOError:
|
||||
pass
|
||||
return ip
|
13
testing/mozbase/mozhttpd/mozhttpd.py → testing/mozbase/mozhttpd/mozhttpd/mozhttpd.py
Normal file → Executable file
13
testing/mozbase/mozhttpd/mozhttpd.py → testing/mozbase/mozhttpd/mozhttpd/mozhttpd.py
Normal file → Executable file
@ -78,23 +78,24 @@ class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
|
||||
class MozHttpd(object):
|
||||
|
||||
def __init__(self, host="127.0.0.1", port=8888, docroot=os.getcwd()):
|
||||
def __init__(self, host="127.0.0.1", port=8888, docroot=os.getcwd(), handler_class=MozRequestHandler):
|
||||
self.host = host
|
||||
self.port = int(port)
|
||||
self.docroot = docroot
|
||||
self.httpd = None
|
||||
|
||||
class MozRequestHandlerInstance(handler_class):
|
||||
docroot = self.docroot
|
||||
|
||||
self.handler_class = MozRequestHandlerInstance
|
||||
|
||||
def start(self, block=False):
|
||||
"""
|
||||
start the server. If block is True, the call will not return.
|
||||
If block is False, the server will be started on a separate thread that
|
||||
can be terminated by a call to .stop()
|
||||
"""
|
||||
|
||||
class MozRequestHandlerInstance(MozRequestHandler):
|
||||
docroot = self.docroot
|
||||
|
||||
self.httpd = EasyServer((self.host, self.port), MozRequestHandlerInstance)
|
||||
self.httpd = EasyServer((self.host, self.port), self.handler_class)
|
||||
if block:
|
||||
self.httpd.serve_forever()
|
||||
else:
|
@ -36,7 +36,7 @@
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
try:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
@ -59,7 +59,7 @@ setup(name='mozhttpd',
|
||||
url='https://github.com/mozilla/mozbase/tree/master/mozhttpd',
|
||||
license='MPL',
|
||||
py_modules=['mozhttpd'],
|
||||
packages=[],
|
||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=deps,
|
||||
|
39
testing/mozbase/mozinfo/mozinfo/__init__.py
Normal file
39
testing/mozbase/mozinfo/mozinfo/__init__.py
Normal file
@ -0,0 +1,39 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozinfo.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2010
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Jeff Hammel <jhammel@mozilla.com>
|
||||
# Clint Talbert <ctalbert@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
from mozinfo import *
|
0
testing/mozbase/mozinfo/mozinfo.py → testing/mozbase/mozinfo/mozinfo/mozinfo.py
Normal file → Executable file
0
testing/mozbase/mozinfo/mozinfo.py → testing/mozbase/mozinfo/mozinfo/mozinfo.py
Normal file → Executable file
@ -37,7 +37,7 @@
|
||||
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.3.3'
|
||||
|
||||
@ -65,8 +65,7 @@ setup(name='mozinfo',
|
||||
author_email='jhammel@mozilla.com',
|
||||
url='https://wiki.mozilla.org/Auto-tools',
|
||||
license='MPL',
|
||||
py_modules=['mozinfo'],
|
||||
packages=[],
|
||||
packages=find_packages(exclude=['legacy']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=deps,
|
||||
|
38
testing/mozbase/mozinstall/mozinstall/__init__.py
Normal file
38
testing/mozbase/mozinstall/mozinstall/__init__.py
Normal file
@ -0,0 +1,38 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozinstall.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Clint Talbert <ctalbert@mozilla.com>
|
||||
# Andrew Halberstadt <halbersa@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
from mozinstall import *
|
@ -37,7 +37,7 @@
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
try:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
@ -66,8 +66,7 @@ setup(name='mozInstall',
|
||||
author_email='mdas@mozilla.com',
|
||||
url='https://github.com/mozilla/mozbase',
|
||||
license='MPL',
|
||||
py_modules=['mozinstall'],
|
||||
packages=[],
|
||||
packages=find_packages(exclude=['legacy']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=deps,
|
||||
|
0
testing/mozbase/mozprocess/mozprocess/pid.py
Normal file → Executable file
0
testing/mozbase/mozprocess/mozprocess/pid.py
Normal file → Executable file
@ -42,12 +42,12 @@ import os
|
||||
import sys
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.1b2'
|
||||
version = '0.1'
|
||||
|
||||
# we only support python 2 right now
|
||||
assert sys.version_info[0] == 2
|
||||
|
||||
deps = ["ManifestDestiny == 0.5.4"]
|
||||
deps = ["ManifestDestiny >= 0.5.4"]
|
||||
# version-dependent dependencies
|
||||
try:
|
||||
import json
|
||||
|
@ -56,7 +56,10 @@ try:
|
||||
import pkg_resources
|
||||
def get_metadata_from_egg(module):
|
||||
ret = {}
|
||||
dist = pkg_resources.get_distribution(module)
|
||||
try:
|
||||
dist = pkg_resources.get_distribution(module)
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return {}
|
||||
if dist.has_metadata("PKG-INFO"):
|
||||
key = None
|
||||
for line in dist.get_metadata("PKG-INFO").splitlines():
|
||||
|
@ -43,7 +43,7 @@ import sys
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
PACKAGE_NAME = "mozrunner"
|
||||
PACKAGE_VERSION = "4.0"
|
||||
PACKAGE_VERSION = "4.1"
|
||||
|
||||
desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
|
||||
# take description from README
|
||||
@ -53,7 +53,10 @@ try:
|
||||
except (OSError, IOError):
|
||||
description = ''
|
||||
|
||||
deps = ['mozprocess', 'mozprofile', 'mozinfo']
|
||||
deps = ['mozinfo',
|
||||
'mozprocess',
|
||||
'mozprofile >= 0.1',
|
||||
]
|
||||
|
||||
# we only support python 2 right now
|
||||
assert sys.version_info[0] == 2
|
||||
|
0
testing/mozbase/setup_development.py
Normal file → Executable file
0
testing/mozbase/setup_development.py
Normal file → Executable file
@ -52,6 +52,7 @@ PEPTEST_HARNESS = \
|
||||
|
||||
PEPTEST_EXTRAS = \
|
||||
setup.py \
|
||||
runtests.py \
|
||||
MANIFEST.in \
|
||||
README.md \
|
||||
$(NULL)
|
||||
@ -60,6 +61,14 @@ PEPTEST_TESTS = \
|
||||
tests \
|
||||
$(NULL)
|
||||
|
||||
_DEST_DIR = $(DEPTH)/_tests/peptest
|
||||
libs:: $(PEPTEST_HARNESS)
|
||||
$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
|
||||
libs:: $(PEPTEST_EXTRAS)
|
||||
$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
|
||||
libs:: $(PEPTEST_TESTS)
|
||||
$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
|
||||
|
||||
stage-package: PKG_STAGE = $(DIST)/test-package-stage
|
||||
stage-package:
|
||||
$(NSINSTALL) -D $(PKG_STAGE)/peptest
|
||||
|
61
testing/peptest/runtests.py
Normal file
61
testing/peptest/runtests.py
Normal file
@ -0,0 +1,61 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is peptest.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# The Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Andrew Halberstadt <halbersa@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
"""
|
||||
Adds peptest's dependencies to sys.path then runs the tests
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
deps = ['manifestdestiny',
|
||||
'mozinfo',
|
||||
'mozhttpd',
|
||||
'mozlog',
|
||||
'mozprofile',
|
||||
'mozprocess',
|
||||
'mozrunner',
|
||||
]
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
mozbase = os.path.realpath(os.path.join(here, '..', 'mozbase'))
|
||||
|
||||
for dep in deps:
|
||||
module = os.path.join(mozbase, dep)
|
||||
if module not in sys.path:
|
||||
sys.path.insert(0, module)
|
||||
|
||||
from peptest import runpeptests
|
||||
runpeptests.main(sys.argv[1:])
|
@ -41,8 +41,10 @@
|
||||
# replaces 'EXTRA_TEST_ARGS=--test-path=...'.
|
||||
ifdef TEST_PATH
|
||||
TEST_PATH_ARG := --test-path=$(TEST_PATH)
|
||||
PEPTEST_PATH_ARG := --test-path=$(TEST_PATH)
|
||||
else
|
||||
TEST_PATH_ARG :=
|
||||
PEPTEST_PATH_ARG := --test-path=_tests/peptest/tests/firefox/firefox_all.ini
|
||||
endif
|
||||
|
||||
# include automation-build.mk to get the path to the binary
|
||||
@ -228,6 +230,16 @@ xpcshell-tests-remote:
|
||||
echo "please prepare your host with environment variables for TEST_DEVICE"; \
|
||||
fi
|
||||
|
||||
# Runs peptest, for usage see: https://developer.mozilla.org/en/Peptest#Running_Tests
|
||||
RUN_PEPTEST = \
|
||||
rm -f ./$@.log && \
|
||||
$(PYTHON) _tests/peptest/runtests.py --binary=$(browser_path) $(PEPTEST_PATH_ARG) \
|
||||
--log-file=./$@.log $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS)
|
||||
|
||||
peptest:
|
||||
$(RUN_PEPTEST)
|
||||
$(CHECK_TEST_ERROR)
|
||||
|
||||
# Package up the tests and test harnesses
|
||||
include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
|
||||
|
||||
@ -291,4 +303,5 @@ stage-mozbase: make-stage-dir
|
||||
reftest crashtest \
|
||||
xpcshell-tests \
|
||||
jstestbrowser \
|
||||
peptest \
|
||||
package-tests make-stage-dir stage-mochitest stage-reftest stage-xpcshell stage-jstests stage-android stage-jetpack stage-firebug stage-peptest stage-mozbase
|
||||
|
Loading…
Reference in New Issue
Block a user