Bug 1280573 - Add testing/mozbase to flake8 linter: r=ahal

added testing/mozbase to tools/lint/flake8.lint
fixed a first batch of PEP8 errors/warnings

at first the commad autopep8 -i --max-line-length 99 -r -j 8 .
has been used to fix simpler problems, run from testing/mozbase

some of the issues can not easily fixed :
- undefined 'names' in code for example isLinux - isLinux and isBsd "fixed" with # noqa
- undefined 'message' resolved with return fmt.format(...
- undefined 'structured' resolved replacing those with mozlog
- long comments - some remaining - addressed with # noqa
- package level import everything - addressed with # flake8: noqa

restored testing/mozbase/mozdevice/mozdevice/Zeroconf.py
fixed issues reported on mozreview
fixed ')' in testing/mozbase/mozprocess/mozprocess/qijo.py imports
finally fixed multiline string at testing/mozbase/manifestparser/tests/test_manifestparser.py:114
^^^ and again, but now with ./mach python-test --path-only testing/mozbase/manifestparser/tests/test_manifestparser.py passing
fixed testing/mozbase/manifestparser/tests/test_convert_directory.py assert

fixed this error:
10:15:21     INFO -      return lambda line: stack_fixer_module.fixSymbols(line)
10:15:21     INFO -  TypeError: fixSymbols() takes exactly 2 arguments (1 given)

fixed two spaces lint error even of #  noqa comments
restored assignement to lambda with #  noqa to silence the lint error
global noqa for testing/mozbase/manifestparser/tests/test_filters.py
stupid is/is not error...

MozReview-Commit-ID: 1FpJF54GqIi

--HG--
extra : rebase_source : 3cf0277fb36a296e3506aeacc2ff05e1b03f9eac
This commit is contained in:
Francesco Pischedda 2016-09-30 16:08:37 +02:00
parent 2d25ee552e
commit f45ed99748
213 changed files with 2244 additions and 1535 deletions

View File

@ -5,13 +5,16 @@ import types
from mozlog import commandline, get_default_logger
class TestAssertion(Exception):
pass
def assert_equals(a, b):
if a != b:
raise TestAssertion("%r not equal to %r" % (a, b))
def expected(status):
def inner(f):
def test_func():
@ -21,26 +24,32 @@ def expected(status):
return test_func
return inner
def test_that_passes():
assert_equals(1, int("1"))
def test_that_fails():
assert_equals(1, int("2"))
def test_that_has_an_error():
assert_equals(2, 1 + "1")
@expected("FAIL")
def test_expected_fail():
assert_equals(2 + 2, 5)
class TestRunner(object):
def __init__(self):
self.logger = get_default_logger(component='TestRunner')
def gather_tests(self):
for item in globals().itervalues():
if type(item) == types.FunctionType and item.__name__.startswith("test_"):
if isinstance(item, types.FunctionType) and item.__name__.startswith("test_"):
yield item.__name__, item
def run(self):
@ -69,10 +78,12 @@ class TestRunner(object):
status = "PASS"
self.logger.test_end(name, status=status, expected=expected, message=message)
def get_parser():
parser = argparse.ArgumentParser()
return parser
def main():
parser = get_parser()
commandline.add_logging_group(parser)

View File

@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import sys
import os
# 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
@ -27,7 +28,7 @@ for item in os.listdir(parent):
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# 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.
@ -40,7 +41,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@ -60,37 +61,37 @@ release = '1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# 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
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# 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
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# 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 = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
@ -111,26 +112,26 @@ if not on_rtd:
# 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 = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = "mozbase documentation"
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# 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
# 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
# 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,
@ -139,44 +140,44 @@ 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'
# 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
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = 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 = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'MozBasedoc'
@ -185,42 +186,42 @@ htmlhelp_basename = 'MozBasedoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# 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', 'MozBase.tex', u'MozBase Documentation',
u'Mozilla Automation and Tools team', 'manual'),
('index', 'MozBase.tex', u'MozBase Documentation',
u'Mozilla Automation and Tools team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@ -233,7 +234,7 @@ man_pages = [
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@ -242,16 +243,16 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'MozBase', u'MozBase Documentation',
u'Mozilla Automation and Tools team', 'MozBase', 'One line description of project.',
'Miscellaneous'),
('index', 'MozBase', u'MozBase Documentation',
u'Mozilla Automation and Tools team', 'MozBase', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'

View File

@ -1,3 +1,4 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -18,7 +18,8 @@ from .manifestparser import (
class ParserError(Exception):
"""error for exceptions while parsing the command line"""
"""error for exceptions while parsing the command line"""
def parse_args(_args):
"""
@ -61,36 +62,41 @@ def parse_args(_args):
# return values
return (_dict, tags, args)
class CLICommand(object):
usage = '%prog [options] command'
def __init__(self, parser):
self._parser = parser # master parser
self._parser = parser # master parser
def parser(self):
return OptionParser(usage=self.usage, description=self.__doc__,
add_help_option=False)
return OptionParser(usage=self.usage, description=self.__doc__,
add_help_option=False)
class Copy(CLICommand):
usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
def __call__(self, options, args):
# parse the arguments
try:
kwargs, tags, args = parse_args(args)
except ParserError, e:
self._parser.error(e.message)
# parse the arguments
try:
kwargs, tags, args = parse_args(args)
except ParserError, e:
self._parser.error(e.message)
# make sure we have some manifests, otherwise it will
# be quite boring
if not len(args) == 2:
HelpCLI(self._parser)(options, ['copy'])
return
# make sure we have some manifests, otherwise it will
# be quite boring
if not len(args) == 2:
HelpCLI(self._parser)(options, ['copy'])
return
# read the manifests
# TODO: should probably ensure these exist here
manifests = ManifestParser()
manifests.read(args[0])
# read the manifests
# TODO: should probably ensure these exist here
manifests = ManifestParser()
manifests.read(args[0])
# print the resultant query
manifests.copy(args[1], None, *tags, **kwargs)
# print the resultant query
manifests.copy(args[1], None, *tags, **kwargs)
class CreateCLI(CLICommand):
@ -134,6 +140,7 @@ class WriteCLI(CLICommand):
write a manifest based on a query
"""
usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...'
def __call__(self, options, args):
# parse the arguments
@ -157,7 +164,6 @@ class WriteCLI(CLICommand):
manifests.write(global_tags=tags, global_kwargs=kwargs)
class HelpCLI(CLICommand):
"""
get help on a command
@ -173,6 +179,7 @@ class HelpCLI(CLICommand):
for command in sorted(commands):
print ' %s : %s' % (command, commands[command].__doc__.strip())
class UpdateCLI(CLICommand):
"""
update the tests as listed in a manifest from a directory
@ -202,10 +209,11 @@ class UpdateCLI(CLICommand):
# command -> class mapping
commands = { 'create': CreateCLI,
'help': HelpCLI,
'update': UpdateCLI,
'write': WriteCLI }
commands = {'create': CreateCLI,
'help': HelpCLI,
'update': UpdateCLI,
'write': WriteCLI}
def main(args=sys.argv[1:]):
"""console_script entry point"""
@ -228,7 +236,8 @@ def main(args=sys.argv[1:]):
# get the command
command = args[0]
if command not in commands:
parser.error("Command must be one of %s (you gave '%s')" % (', '.join(sorted(commands.keys())), command))
parser.error("Command must be one of %s (you gave '%s')" %
(', '.join(sorted(commands.keys())), command))
handler = commands[command](parser)
handler(options, args[1:])

View File

@ -45,92 +45,126 @@ __all__ = ['parse', 'ParseError', 'ExpressionParser']
# - lbp: left binding power
# - rbp: right binding power
class ident_token(object):
def __init__(self, scanner, value):
self.value = value
def nud(self, parser):
# identifiers take their value from the value mappings passed
# to the parser
return parser.value(self.value)
class literal_token(object):
def __init__(self, scanner, value):
self.value = value
def nud(self, parser):
return self.value
class eq_op_token(object):
"=="
def led(self, parser, left):
return left == parser.expression(self.lbp)
class neq_op_token(object):
"!="
def led(self, parser, left):
return left != parser.expression(self.lbp)
class lt_op_token(object):
"<"
def led(self, parser, left):
return left < parser.expression(self.lbp)
class gt_op_token(object):
">"
def led(self, parser, left):
return left > parser.expression(self.lbp)
class le_op_token(object):
"<="
def led(self, parser, left):
return left <= parser.expression(self.lbp)
class ge_op_token(object):
">="
def led(self, parser, left):
return left >= parser.expression(self.lbp)
class not_op_token(object):
"!"
def nud(self, parser):
return not parser.expression(100)
class and_op_token(object):
"&&"
def led(self, parser, left):
right = parser.expression(self.lbp)
return left and right
class or_op_token(object):
"||"
def led(self, parser, left):
right = parser.expression(self.lbp)
return left or right
class lparen_token(object):
"("
def nud(self, parser):
expr = parser.expression()
parser.advance(rparen_token)
return expr
class rparen_token(object):
")"
class end_token(object):
"""always ends parsing"""
### derived literal tokens
# derived literal tokens
class bool_token(literal_token):
def __init__(self, scanner, value):
value = {'true':True, 'false':False}[value]
value = {'true': True, 'false': False}[value]
literal_token.__init__(self, scanner, value)
class int_token(literal_token):
def __init__(self, scanner, value):
literal_token.__init__(self, scanner, int(value))
class string_token(literal_token):
def __init__(self, scanner, value):
literal_token.__init__(self, scanner, value[1:-1])
@ -143,11 +177,13 @@ precedence = [(end_token, rparen_token),
]
for index, rank in enumerate(precedence):
for token in rank:
token.lbp = index # lbp = lowest left binding power
token.lbp = index # lbp = lowest left binding power
class ParseError(Exception):
"""error parsing conditional expression"""
class ExpressionParser(object):
"""
A parser for a simple expression language.
@ -215,7 +251,7 @@ class ExpressionParser(object):
(r"&&", and_op_token()),
(r"\(", lparen_token()),
(r"\)", rparen_token()),
(r"\s+", None), # skip whitespace
(r"\s+", None), # skip whitespace
])
tokens, remainder = ExpressionParser.scanner.scan(self.text)
for t in tokens:
@ -238,7 +274,7 @@ class ExpressionParser(object):
to the next token.
"""
if not isinstance(self.token, expected):
raise Exception, "Unexpected token!"
raise Exception("Unexpected token!")
self.token = self.iter.next()
def expression(self, rbp=0):
@ -268,7 +304,10 @@ class ExpressionParser(object):
except:
extype, ex, tb = sys.exc_info()
formatted = ''.join(traceback.format_exception_only(extype, ex))
raise ParseError("could not parse: %s\nexception: %svariables: %s" % (self.text, formatted, self.valuemapping)), None, tb
raise ParseError("could not parse: "
"%s\nexception: %svariables: %s" % (self.text,
formatted,
self.valuemapping)), None, tb
__call__ = parse

View File

@ -117,6 +117,7 @@ class subsuite(InstanceFilter):
:param name: The name of the subsuite to run (default None)
"""
def __init__(self, name=None):
InstanceFilter.__init__(self, name=name)
self.name = name
@ -227,7 +228,7 @@ class chunk_by_dir(InstanceFilter):
path = path[1:]
dirs = path.split(os.sep)
dirs = dirs[:min(self.depth, len(dirs)-1)]
dirs = dirs[:min(self.depth, len(dirs) - 1)]
path = os.sep.join(dirs)
# don't count directories that only have disabled tests in them,
@ -303,7 +304,7 @@ class chunk_by_runtime(InstanceFilter):
tests_by_chunk[0][0] += runtime
tests_by_chunk[0][1].extend(batch)
return (t for t in tests_by_chunk[self.this_chunk-1][1])
return (t for t in tests_by_chunk[self.this_chunk - 1][1])
class tags(InstanceFilter):

View File

@ -2,9 +2,10 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import os
__all__ = ['read_ini']
import os
def read_ini(fp, variables=None, default='DEFAULT', defaults_only=False,
comments=';#', separators=('=', ':'),
@ -58,7 +59,8 @@ def read_ini(fp, variables=None, default='DEFAULT', defaults_only=False,
if strict:
# make sure this section doesn't already exist
assert section not in section_names, "Section '%s' already found in '%s'" % (section, section_names)
assert section not in section_names, "Section '%s' already found in '%s'" % (
section, section_names)
section_names.add(section)
current_section = {}
@ -119,7 +121,8 @@ def read_ini(fp, variables=None, default='DEFAULT', defaults_only=False,
('support-files', '%s %s')):
local_value, global_value = local_dict.get(field_name), variables.get(field_name)
if local_value and global_value:
local_dict[field_name] = pattern % (global_value.split('#')[0], local_value.split('#')[0])
local_dict[field_name] = pattern % (
global_value.split('#')[0], local_value.split('#')[0])
variables.update(local_dict)
return variables

View File

@ -2,8 +2,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
__all__ = ['ManifestParser', 'TestManifest', 'convert']
from StringIO import StringIO
import json
import fnmatch
@ -20,11 +18,13 @@ from .filters import (
filterlist,
)
__all__ = ['ManifestParser', 'TestManifest', 'convert']
relpath = os.path.relpath
string = (basestring,)
### path normalization
# path normalization
def normalize_path(path):
"""normalize a relative path"""
@ -32,6 +32,7 @@ def normalize_path(path):
return path.replace('/', os.path.sep)
return path
def denormalize_path(path):
"""denormalize a relative path"""
if sys.platform.startswith('win'):
@ -39,7 +40,7 @@ def denormalize_path(path):
return path
### objects for parsing manifests
# objects for parsing manifests
class ManifestParser(object):
"""read .ini manifests"""
@ -82,7 +83,7 @@ class ManifestParser(object):
return self.finder.get(path) is not None
return os.path.exists(path)
### methods for reading manifests
# methods for reading manifests
def _read(self, root, filename, defaults, defaults_only=False, parentmanifest=None):
"""
@ -186,7 +187,7 @@ class ManifestParser(object):
# determine the path
path = test.get('path', section)
_relpath = path
if '://' not in path: # don't futz with URLs
if '://' not in path: # don't futz with URLs
path = normalize_path(path)
if here and not os.path.isabs(path):
# Profiling indicates 25% of manifest parsing is spent
@ -260,7 +261,7 @@ class ManifestParser(object):
here = None
if isinstance(filename, string):
here = os.path.dirname(os.path.abspath(filename))
defaults['here'] = here # directory of master .ini file
defaults['here'] = here # directory of master .ini file
if self.rootdir is None:
# set the root directory
@ -269,8 +270,7 @@ class ManifestParser(object):
self._read(here, filename, defaults)
### methods for querying manifests
# methods for querying manifests
def query(self, *checks, **kw):
"""
@ -304,14 +304,18 @@ class ManifestParser(object):
# make some check functions
if inverse:
has_tags = lambda test: not tags.intersection(test.keys())
def has_tags(test):
return not tags.intersection(test.keys())
def dict_query(test):
for key, value in kwargs.items():
if test.get(key) == value:
return False
return True
else:
has_tags = lambda test: tags.issubset(test.keys())
def has_tags(test):
return tags.issubset(test.keys())
def dict_query(test):
for key, value in kwargs.items():
if test.get(key) != value:
@ -349,8 +353,7 @@ class ManifestParser(object):
def paths(self):
return [i['path'] for i in self.tests]
### methods for auditing
# methods for auditing
def missing(self, tests=None):
"""
@ -370,7 +373,7 @@ class ManifestParser(object):
"The following test(s) are missing: %s" %
json.dumps(missing_paths, indent=2))
print >> sys.stderr, "Warning: The following test(s) are missing: %s" % \
json.dumps(missing_paths, indent=2)
json.dumps(missing_paths, indent=2)
return missing
def verifyDirectory(self, directories, pattern=None, extensions=None):
@ -404,8 +407,7 @@ class ManifestParser(object):
missing_from_manifest = files.difference(paths)
return (missing_from_filesystem, missing_from_manifest)
### methods for output
# methods for output
def write(self, fp=sys.stdout, rootdir=None,
global_tags=None, global_kwargs=None,
@ -454,7 +456,7 @@ class ManifestParser(object):
print >> fp
for test in tests:
test = test.copy() # don't overwrite
test = test.copy() # don't overwrite
path = test['name']
if not os.path.isabs(path):
@ -509,7 +511,7 @@ class ManifestParser(object):
# tests to copy
tests = self.get(tags=tags, **kwargs)
if not tests:
return # nothing to do!
return # nothing to do!
# root directory
if rootdir is None:
@ -567,7 +569,7 @@ class ManifestParser(object):
destination = os.path.join(rootdir, _relpath)
shutil.copy(source, destination)
### directory importers
# directory importers
@classmethod
def _walk_directories(cls, directories, callback, pattern=None, ignore=()):
@ -582,7 +584,8 @@ class ManifestParser(object):
ignore = set(ignore)
if not patterns:
accept_filename = lambda filename: True
def accept_filename(filename):
return True
else:
def accept_filename(filename):
for pattern in patterns:
@ -590,9 +593,11 @@ class ManifestParser(object):
return True
if not ignore:
accept_dirname = lambda dirname: True
def accept_dirname(dirname):
return True
else:
accept_dirname = lambda dirname: dirname not in ignore
def accept_dirname(dirname):
return dirname not in ignore
rootdirectories = directories[:]
seen_directories = set()
@ -632,12 +637,12 @@ class ManifestParser(object):
if subdirs or files:
callback(rootdirectory, directory, subdirs, files)
@classmethod
def populate_directory_manifests(cls, directories, filename, pattern=None, ignore=(), overwrite=False):
def populate_directory_manifests(cls, directories, filename, pattern=None, ignore=(),
overwrite=False):
"""
walks directories and writes manifests of name `filename` in-place; returns `cls` instance populated
with the given manifests
walks directories and writes manifests of name `filename` in-place;
returns `cls` instance populated with the given manifests
filename -- filename of manifests to write
pattern -- shell pattern (glob) or patterns of filenames to match
@ -692,10 +697,9 @@ class ManifestParser(object):
if false then the paths are absolute
"""
# determine output
opened_manifest_file = None # name of opened manifest file
absolute = not relative_to # whether to output absolute path names as names
opened_manifest_file = None # name of opened manifest file
absolute = not relative_to # whether to output absolute path names as names
if isinstance(write, string):
opened_manifest_file = write
write = file(write, 'w')
@ -718,8 +722,7 @@ class ManifestParser(object):
# write to manifest
print >> write, '\n'.join(['[%s]' % denormalize_path(filename)
for filename in filenames])
for filename in filenames])
cls._walk_directories(directories, callback, pattern=pattern, ignore=ignore)
@ -734,7 +737,6 @@ class ManifestParser(object):
write.seek(0)
manifests = [write]
# make a ManifestParser instance
return cls(manifests=manifests)
@ -762,7 +764,7 @@ class TestManifest(ManifestParser):
:param filters: list of filters to apply to the tests
:returns: list of test objects that were not filtered out
"""
tests = [i.copy() for i in self.tests] # shallow copy
tests = [i.copy() for i in self.tests] # shallow copy
# mark all tests as passing
for test in tests:

View File

@ -11,7 +11,7 @@ setup(name=PACKAGE_NAME,
version=PACKAGE_VERSION,
description="Library to create and manage test manifests",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla manifests',
author='Mozilla Automation and Testing Team',
author_email='tools@lists.mozilla.org',
@ -24,4 +24,4 @@ setup(name=PACKAGE_NAME,
[console_scripts]
manifestparser = manifestparser.cli:main
""",
)
)

View File

@ -33,7 +33,7 @@ class ChunkBySlice(TestCase):
for total in range(1, num_tests + 1):
res = []
res_disabled = []
for chunk in range(1, total+1):
for chunk in range(1, total + 1):
f = chunk_by_slice(chunk, total)
res.append(list(f(tests, {})))
if disabled:
@ -96,20 +96,20 @@ class ChunkByDir(TestCase):
def run_all_combos(self, dirs):
tests = list(self.generate_tests(dirs))
deepest = max(len(t['relpath'].split(os.sep))-1 for t in tests)
for depth in range(1, deepest+1):
deepest = max(len(t['relpath'].split(os.sep)) - 1 for t in tests)
for depth in range(1, deepest + 1):
def num_groups(tests):
unique = set()
for p in [t['relpath'] for t in tests]:
p = p.split(os.sep)
p = p[:min(depth, len(p)-1)]
p = p[:min(depth, len(p) - 1)]
unique.add(os.sep.join(p))
return len(unique)
for total in range(1, num_groups(tests)+1):
for total in range(1, num_groups(tests) + 1):
res = []
for this in range(1, total+1):
for this in range(1, total + 1):
f = chunk_by_dir(this, total, depth)
res.append(list(f(tests, {})))
@ -208,7 +208,7 @@ class ChunkByRuntime(TestCase):
chunks[i].extend(batch)
# "draft" style (last pick goes first in the next round)
if (i == 0 and d == -1) or (i == total-1 and d == 1):
if (i == 0 and d == -1) or (i == total - 1 and d == 1):
d = -d
else:
i += d
@ -225,9 +225,9 @@ class ChunkByRuntime(TestCase):
tests = list(self.generate_tests(dirs))
runtimes = self.get_runtimes(tests)
for total in range(1, len(dirs)+1):
for total in range(1, len(dirs) + 1):
chunks = []
for this in range(1, total+1):
for this in range(1, total + 1):
f = chunk_by_runtime(this, total, runtimes)
ret = list(f(tests, {}))
chunks.append(ret)

View File

@ -20,12 +20,15 @@ here = os.path.dirname(os.path.abspath(__file__))
#
# Workaround is to use the following function, if absolute path of temp dir
# must be compared.
def create_realpath_tempdir():
"""
Create a tempdir without symlinks.
"""
return os.path.realpath(tempfile.mkdtemp())
class TestDirectoryConversion(unittest.TestCase):
"""test conversion of a directory tree to a manifest structure"""
@ -56,8 +59,7 @@ class TestDirectoryConversion(unittest.TestCase):
# Make a manifest for it
manifest = convert([stub])
self.assertEqual(str(manifest),
"""[%(stub)s/bar]
out_tmpl = """[%(stub)s/bar]
subsuite =
[%(stub)s/fleem]
@ -69,11 +71,12 @@ subsuite =
[%(stub)s/subdir/subfile]
subsuite =
""" % dict(stub=stub))
""" # noqa
self.assertEqual(str(manifest), out_tmpl % dict(stub=stub))
except:
raise
finally:
shutil.rmtree(stub) # cleanup
shutil.rmtree(stub) # cleanup
def test_convert_directory_manifests_in_place(self):
"""
@ -103,7 +106,8 @@ subsuite =
stub = self.create_stub()
try:
ManifestParser.populate_directory_manifests([stub], filename='manifest.ini', ignore=('subdir',))
ManifestParser.populate_directory_manifests(
[stub], filename='manifest.ini', ignore=('subdir',))
parser = ManifestParser()
parser.read(os.path.join(stub, 'manifest.ini'))
self.assertEqual([i['name'] for i in parser.tests],
@ -162,15 +166,15 @@ subsuite =
self.assertEqual(manifest.get('name', name='1'), ['1'])
manifest.update(tempdir, name='1')
self.assertEqual(sorted(os.listdir(newtempdir)),
['1', 'manifest.ini'])
['1', 'manifest.ini'])
# Update that one file and copy all the "tests":
file(os.path.join(tempdir, '1'), 'w').write('secret door')
manifest.update(tempdir)
self.assertEqual(sorted(os.listdir(newtempdir)),
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'manifest.ini'])
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'manifest.ini'])
self.assertEqual(file(os.path.join(newtempdir, '1')).read().strip(),
'secret door')
'secret door')
# clean up:
shutil.rmtree(tempdir)

View File

@ -11,6 +11,7 @@ import unittest
from manifestparser import convert, ManifestParser
class TestSymlinkConversion(unittest.TestCase):
"""
test conversion of a directory tree with symlinks to a manifest structure
@ -125,6 +126,7 @@ class TestSymlinkConversion(unittest.TestCase):
open(os.path.join(workspace, 'dir1', 'ldir2', 'f2.txt'), 'a').close()
data = []
def callback(rootdirectory, directory, subdirs, files):
for f in files:
data.append(f)

View File

@ -10,10 +10,10 @@ from manifestparser import ManifestParser
here = os.path.dirname(os.path.abspath(__file__))
class TestDefaultSkipif(unittest.TestCase):
"""Tests applying a skip-if condition in [DEFAULT] and || with the value for the test"""
def test_defaults(self):
default = os.path.join(here, 'default-skipif.ini')
@ -26,12 +26,14 @@ class TestDefaultSkipif(unittest.TestCase):
elif test['name'] == 'test3':
self.assertEqual(test['skip-if'], "(os == 'win' && debug ) || (os == 'win')")
elif test['name'] == 'test4':
self.assertEqual(test['skip-if'], "(os == 'win' && debug ) || (os == 'win' && debug)")
self.assertEqual(
test['skip-if'], "(os == 'win' && debug ) || (os == 'win' && debug)")
elif test['name'] == 'test5':
self.assertEqual(test['skip-if'], "os == 'win' && debug # a pesky comment")
elif test['name'] == 'test6':
self.assertEqual(test['skip-if'], "(os == 'win' && debug ) || (debug )")
class TestDefaultSupportFiles(unittest.TestCase):
"""Tests combining support-files field in [DEFAULT] with the value for a test"""

View File

@ -3,6 +3,7 @@
import unittest
from manifestparser import parse
class ExpressionParserTest(unittest.TestCase):
"""Test the conditional expression parser."""
@ -64,7 +65,6 @@ class ExpressionParserTest(unittest.TestCase):
self.assertTrue(parse("true && (true || false)"))
self.assertTrue(parse("(true && false) || (true && (true || false))"))
def test_comments(self):
# comments in expressions work accidentally, via an implementation
# detail - the '#' character doesn't match any of the regular

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python
# flake8: noqa
from copy import deepcopy
import os
@ -144,7 +145,7 @@ class BuiltinFilters(unittest.TestCase):
tests = deepcopy(self.tests)
tests = list(sub1(tests, {}))
self.assertNotIn(self.tests[5], tests)
self.assertEquals(len(tests), len(self.tests)-1)
self.assertEquals(len(tests), len(self.tests) - 1)
tests = deepcopy(self.tests)
tests = list(sub2(tests, {}))

View File

@ -13,6 +13,7 @@ from StringIO import StringIO
here = os.path.dirname(os.path.abspath(__file__))
class TestManifestParser(unittest.TestCase):
"""
Test the manifest parser
@ -41,7 +42,8 @@ class TestManifestParser(unittest.TestCase):
self.assertTrue(len(restart_tests) < len(parser.tests))
self.assertEqual(len(restart_tests), len(parser.get(manifest=mozmill_restart_example)))
self.assertFalse([test for test in restart_tests
if test['manifest'] != os.path.join(here, 'mozmill-restart-example.ini')])
if test['manifest'] != os.path.join(here,
'mozmill-restart-example.ini')])
self.assertEqual(parser.get('name', tags=['foo']),
['restartTests/testExtensionInstallUninstall/test2.js',
'restartTests/testExtensionInstallUninstall/test1.js'])
@ -57,21 +59,22 @@ class TestManifestParser(unittest.TestCase):
# All of the tests should be included, in order:
self.assertEqual(parser.get('name'),
['crash-handling', 'fleem', 'flowers'])
self.assertEqual([(test['name'], os.path.basename(test['manifest'])) for test in parser.tests],
[('crash-handling', 'bar.ini'), ('fleem', 'include-example.ini'), ('flowers', 'foo.ini')])
self.assertEqual([(test['name'], os.path.basename(test['manifest']))
for test in parser.tests],
[('crash-handling', 'bar.ini'),
('fleem', 'include-example.ini'),
('flowers', 'foo.ini')])
# The including manifest is always reported as a part of the generated test object.
self.assertTrue(all([t['ancestor-manifest'] == include_example
for t in parser.tests if t['name'] != 'fleem']))
# The manifests should be there too:
self.assertEqual(len(parser.manifests()), 3)
# We already have the root directory:
self.assertEqual(here, parser.rootdir)
# DEFAULT values should persist across includes, unless they're
# overwritten. In this example, include-example.ini sets foo=bar, but
# it's overridden to fleem in bar.ini
@ -81,7 +84,7 @@ class TestManifestParser(unittest.TestCase):
['crash-handling'])
# Passing parameters in the include section allows defining variables in
#the submodule scope:
# the submodule scope:
self.assertEqual(parser.get('name', tags=['red']),
['flowers'])
@ -107,15 +110,27 @@ class TestManifestParser(unittest.TestCase):
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'foo': 'bar'})
expected_output = """[DEFAULT]
foo = bar
[fleem]
subsuite =
[include/flowers]
blue = ocean
red = roses
subsuite =
yellow = submarine""" # noqa
self.assertEqual(buffer.getvalue().strip(),
'[DEFAULT]\nfoo = bar\n\n[fleem]\nsubsuite = \n\n[include/flowers]\nblue = ocean\nred = roses\nsubsuite = \nyellow = submarine')
expected_output)
def test_invalid_path(self):
"""
Test invalid path should not throw when not strict
"""
manifest = os.path.join(here, 'include-invalid.ini')
parser = ManifestParser(manifests=(manifest,), strict=False)
ManifestParser(manifests=(manifest,), strict=False)
def test_parent_inheritance(self):
"""
@ -130,7 +145,8 @@ class TestManifestParser(unittest.TestCase):
# Parent manifest test should not be included
self.assertEqual(parser.get('name'),
['test_3'])
self.assertEqual([(test['name'], os.path.basename(test['manifest'])) for test in parser.tests],
self.assertEqual([(test['name'], os.path.basename(test['manifest']))
for test in parser.tests],
[('test_3', 'level_3.ini')])
# DEFAULT values should be the ones from level 1
@ -154,7 +170,8 @@ class TestManifestParser(unittest.TestCase):
# Parent manifest test should not be included
self.assertEqual(parser.get('name'),
['test_3'])
self.assertEqual([(test['name'], os.path.basename(test['manifest'])) for test in parser.tests],
self.assertEqual([(test['name'], os.path.basename(test['manifest']))
for test in parser.tests],
[('test_3', 'level_3_default.ini')])
# DEFAULT values should be the ones from level 3
@ -191,7 +208,7 @@ class TestManifestParser(unittest.TestCase):
# A regular variable will inherit its value directly
self.assertEqual(parser.get('name', **{'other-root': '../root'}),
['test_3'])
['test_3'])
# server-root will expand its value as an absolute path
# we will not find anything for the original value

View File

@ -15,6 +15,7 @@ from manifestparser import read_ini
from ConfigParser import ConfigParser
from StringIO import StringIO
class IniParserTest(unittest.TestCase):
def test_inline_comments(self):

View File

@ -18,9 +18,11 @@ class TestTestManifest(unittest.TestCase):
# Test filtering based on platform:
filter_example = os.path.join(here, 'filter-example.ini')
manifest = TestManifest(manifests=(filter_example,), strict=False)
self.assertEqual([i['name'] for i in manifest.active_tests(os='win', disabled=False, exists=False)],
self.assertEqual([i['name'] for i in manifest.active_tests(os='win', disabled=False,
exists=False)],
['windowstest', 'fleem'])
self.assertEqual([i['name'] for i in manifest.active_tests(os='linux', disabled=False, exists=False)],
self.assertEqual([i['name'] for i in manifest.active_tests(os='linux', disabled=False,
exists=False)],
['fleem', 'linuxtest'])
# Look for existing tests. There is only one:

View File

@ -1,8 +1,10 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
mozcrash is a library for getting a stack trace out of processes that have crashed and left behind a minidump file using the Google Breakpad library.
mozcrash is a library for getting a stack trace out of processes that have crashed
and left behind a minidump file using the Google Breakpad library.
"""
from mozcrash import *

View File

@ -2,14 +2,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
__all__ = [
'check_for_crashes',
'check_for_java_exception',
'kill_and_get_minidump',
'log_crashes',
'cleanup_pending_crash_reports',
]
import glob
import os
import re
@ -26,6 +18,14 @@ import mozfile
import mozinfo
import mozlog
__all__ = [
'check_for_crashes',
'check_for_java_exception',
'kill_and_get_minidump',
'log_crashes',
'cleanup_pending_crash_reports',
]
StackInfo = namedtuple("StackInfo",
["minidump_path",
@ -193,7 +193,8 @@ class CrashInfo(object):
glob.glob(os.path.join(self.dump_directory, '*.dmp'))]
max_dumps = 10
if len(self._dump_files) > max_dumps:
self.logger.warning("Found %d dump files -- limited to %d!" % (len(self._dump_files), max_dumps))
self.logger.warning("Found %d dump files -- limited to %d!" %
(len(self._dump_files), max_dumps))
del self._dump_files[max_dumps:]
return self._dump_files
@ -238,7 +239,7 @@ class CrashInfo(object):
retcode = None
if (self.symbols_path and self.stackwalk_binary and
os.path.exists(self.stackwalk_binary) and
os.access(self.stackwalk_binary, os.X_OK)):
os.access(self.stackwalk_binary, os.X_OK)):
command = [
self.stackwalk_binary,
@ -262,12 +263,13 @@ class CrashInfo(object):
# Examples:
# 0 libc.so + 0xa888
# 0 libnss3.so!nssCertificate_Destroy [certificate.c : 102 + 0x0]
# 0 mozjs.dll!js::GlobalObject::getDebuggers() [GlobalObject.cpp:89df18f9b6da : 580 + 0x0]
# 0 libxul.so!void js::gc::MarkInternal<JSObject>(JSTracer*, JSObject**) [Marking.cpp : 92 + 0x28]
# 0 mozjs.dll!js::GlobalObject::getDebuggers() [GlobalObject.cpp:89df18f9b6da : 580 + 0x0] # noqa
# 0 libxul.so!void js::gc::MarkInternal<JSObject>(JSTracer*, JSObject**)
# [Marking.cpp : 92 + 0x28]
lines = out.splitlines()
for i, line in enumerate(lines):
if "(crashed)" in line:
match = re.search(r"^ 0 (?:.*!)?(?:void )?([^\[]+)", lines[i+1])
match = re.search(r"^ 0 (?:.*!)?(?:void )?([^\[]+)", lines[i + 1])
if match:
signature = "@ %s" % match.group(1).strip()
break
@ -325,7 +327,7 @@ def check_for_java_exception(logcat, quiet=False):
logcat output.
Example:
PROCESS-CRASH | java-exception | java.lang.NullPointerException at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)
PROCESS-CRASH | java-exception | java.lang.NullPointerException at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833) # noqa
`logcat` should be a list of strings.
@ -339,26 +341,28 @@ def check_for_java_exception(logcat, quiet=False):
for i, line in enumerate(logcat):
# Logs will be of form:
#
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 ("GeckoBackgroundThread")
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 ("GeckoBackgroundThread") # noqa
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): at android.os.Handler.handleCallback(Handler.java:587)
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833) # noqa
# 01-30 20:15:41.937 E/GeckoAppShell( 1703): at android.os.Handler.handleCallback(Handler.java:587) # noqa
if "REPORTING UNCAUGHT EXCEPTION" in line or "FATAL EXCEPTION" in line:
# Strip away the date, time, logcat tag and pid from the next two lines and
# concatenate the remainder to form a concise summary of the exception.
found_exception = True
if len(logcat) >= i + 3:
logre = re.compile(r".*\): \t?(.*)")
m = logre.search(logcat[i+1])
m = logre.search(logcat[i + 1])
if m and m.group(1):
exception_type = m.group(1)
m = logre.search(logcat[i+2])
m = logre.search(logcat[i + 2])
if m and m.group(1):
exception_location = m.group(1)
if not quiet:
print "PROCESS-CRASH | java-exception | %s %s" % (exception_type, exception_location)
print "PROCESS-CRASH | java-exception | %s %s" % (exception_type,
exception_location)
else:
print "Automation Error: java exception in logcat at line %d of %d: %s" % (i, len(logcat), line)
print "Automation Error: java exception in logcat at line " \
"%d of %d: %s" % (i, len(logcat), line)
break
return found_exception
@ -390,7 +394,7 @@ if mozinfo.isWin:
str(uuid.uuid4()) + ".dmp")
if (mozinfo.info['bits'] != ctypes.sizeof(ctypes.c_voidp) * 8 and
utility_path):
utility_path):
# We're not going to be able to write a minidump with ctypes if our
# python process was compiled for a different architecture than
# firefox, so we invoke the minidumpwriter utility program.
@ -465,6 +469,7 @@ else:
"""
os.kill(pid, signal.SIGKILL)
def kill_and_get_minidump(pid, dump_directory, utility_path=None):
"""
Attempt to kill a process and leave behind a minidump describing its
@ -492,6 +497,7 @@ def kill_and_get_minidump(pid, dump_directory, utility_path=None):
if needs_killing:
kill_pid(pid)
def cleanup_pending_crash_reports():
"""
Delete any pending crash reports.

View File

@ -13,9 +13,10 @@ deps = ['mozfile >= 1.0',
setup(name=PACKAGE_NAME,
version=PACKAGE_VERSION,
description="Library for printing stack traces from minidumps left behind by crashed processes",
description="Library for printing stack traces from minidumps "
"left behind by crashed processes",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Tools team',
author_email='tools@lists.mozilla.org',

View File

@ -4,7 +4,14 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import os, unittest, subprocess, tempfile, shutil, urlparse, zipfile, StringIO
import os
import unittest
import subprocess
import tempfile
import shutil
import urlparse
import zipfile
import StringIO
import mozcrash
import mozhttpd
import mozlog.unstructured as mozlog
@ -12,12 +19,14 @@ import mozlog.unstructured as mozlog
# Make logs go away
log = mozlog.getLogger("mozcrash", handler=mozlog.FileHandler(os.devnull))
def popen_factory(stdouts):
"""
Generate a class that can mock subprocess.Popen. |stdouts| is an iterable that
should return an iterable for the stdout of each process in turn.
"""
class mock_popen(object):
def __init__(self, args, *args_rest, **kwargs):
self.stdout = stdouts.next()
self.returncode = 0
@ -30,7 +39,9 @@ def popen_factory(stdouts):
return mock_popen
class TestCrash(unittest.TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
# a fake file to use as a stackwalk binary
@ -173,56 +184,67 @@ class TestCrash(unittest.TestCase):
z.writestr("symbols.txt", "abc/xyz")
z.close()
return data.getvalue()
def get_symbols(req):
headers = {}
return (200, headers, make_zipfile())
httpd = mozhttpd.MozHttpd(port=0,
urlhandlers=[{'method':'GET', 'path':'/symbols', 'function':get_symbols}])
urlhandlers=[{'method': 'GET',
'path': '/symbols',
'function': get_symbols}])
httpd.start()
symbol_url = urlparse.urlunsplit(('http', '%s:%d' % httpd.httpd.server_address,
'/symbols','',''))
'/symbols', '', ''))
self.assert_(mozcrash.check_for_crashes(self.tempdir,
symbol_url,
stackwalk_binary=self.stackwalk,
quiet=True))
class TestJavaException(unittest.TestCase):
def setUp(self):
self.test_log = ["01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")",
"01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException",
"01-30 20:15:41.937 E/GeckoAppShell( 1703): at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)",
"01-30 20:15:41.937 E/GeckoAppShell( 1703): at android.os.Handler.handleCallback(Handler.java:587)"]
def test_uncaught_exception(self):
"""
Test for an exception which should be caught
"""
self.assert_(mozcrash.check_for_java_exception(self.test_log, quiet=True))
def setUp(self):
self.test_log = [
"01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> "
"REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")",
"01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException",
"01-30 20:15:41.937 E/GeckoAppShell( 1703):"
" at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)",
"01-30 20:15:41.937 E/GeckoAppShell( 1703):"
" at android.os.Handler.handleCallback(Handler.java:587)"]
def test_fatal_exception(self):
"""
Test for an exception which should be caught
"""
fatal_log = list(self.test_log)
fatal_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> FATAL EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")"
self.assert_(mozcrash.check_for_java_exception(fatal_log, quiet=True))
def test_uncaught_exception(self):
"""
Test for an exception which should be caught
"""
self.assert_(mozcrash.check_for_java_exception(self.test_log, quiet=True))
def test_truncated_exception(self):
"""
Test for an exception which should be caught which
was truncated
"""
truncated_log = list(self.test_log)
truncated_log[0], truncated_log[1] = truncated_log[1], truncated_log[0]
self.assert_(mozcrash.check_for_java_exception(truncated_log, quiet=True))
def test_fatal_exception(self):
"""
Test for an exception which should be caught
"""
fatal_log = list(self.test_log)
fatal_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703):" \
" >>> FATAL EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")"
self.assert_(mozcrash.check_for_java_exception(fatal_log, quiet=True))
def test_unchecked_exception(self):
"""
Test for an exception which should not be caught
"""
passable_log = list(self.test_log)
passable_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> NOT-SO-BAD EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")"
self.assert_(not mozcrash.check_for_java_exception(passable_log, quiet=True))
def test_truncated_exception(self):
"""
Test for an exception which should be caught which
was truncated
"""
truncated_log = list(self.test_log)
truncated_log[0], truncated_log[1] = truncated_log[1], truncated_log[0]
self.assert_(mozcrash.check_for_java_exception(truncated_log, quiet=True))
def test_unchecked_exception(self):
"""
Test for an exception which should not be caught
"""
passable_log = list(self.test_log)
passable_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703):" \
" >>> NOT-SO-BAD EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")"
self.assert_(not mozcrash.check_for_java_exception(passable_log, quiet=True))
if __name__ == '__main__':
unittest.main()

View File

@ -1,3 +1,4 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -61,13 +61,14 @@ _DEBUGGER_INFO = {
# Maps each OS platform to the preferred debugger programs found in _DEBUGGER_INFO.
_DEBUGGER_PRIORITIES = {
'win': ['devenv.exe', 'wdexpress.exe'],
'linux': ['gdb', 'cgdb', 'lldb'],
'mac': ['lldb', 'gdb'],
'android': ['gdb'],
'unknown': ['gdb']
'win': ['devenv.exe', 'wdexpress.exe'],
'linux': ['gdb', 'cgdb', 'lldb'],
'mac': ['lldb', 'gdb'],
'android': ['gdb'],
'unknown': ['gdb']
}
def _windbg_installation_paths():
programFilesSuffixes = ['', ' (x86)']
programFiles = "C:/Program Files"
@ -81,6 +82,7 @@ def _windbg_installation_paths():
yield os.path.join(windowsKitsPrefix, version,
'Debuggers', 'x86', 'windbg.exe')
def get_debugger_path(debugger):
'''
Get the full path of the debugger.
@ -105,7 +107,8 @@ def get_debugger_path(debugger):
return find_executable(debugger)
def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False):
def get_debugger_info(debugger, debuggerArgs=None, debuggerInteractive=False):
'''
Get the information about the requested debugger.
@ -127,7 +130,7 @@ def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False
# Append '.exe' to the debugger on Windows if it's not present,
# so things like '--debugger=devenv' work.
if (os.name == 'nt'
and not debugger.lower().endswith('.exe')):
and not debugger.lower().endswith('.exe')):
debugger += '.exe'
debuggerPath = get_debugger_path(debugger)
@ -186,9 +189,12 @@ def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False
return d
# Defines the search policies to use in get_default_debugger_name.
class DebuggerSearch:
OnlyFirst = 1
KeepLooking = 2
OnlyFirst = 1
KeepLooking = 2
def get_default_debugger_name(search=DebuggerSearch.OnlyFirst):
'''
@ -259,6 +265,8 @@ def get_default_debugger_name(search=DebuggerSearch.OnlyFirst):
# --px-file-backed=unwindregs-at-mem-access
# [these reduce PX overheads as described above]
#
def get_default_valgrind_args():
return (['--fair-sched=yes',
'--smc-check=all-non-file',
@ -269,13 +277,15 @@ def get_default_valgrind_args():
+ '/usr/bin/hg,/bin/rm,*/bin/certutil,*/bin/pk12util,'
+ '*/bin/ssltunnel,*/bin/uname,*/bin/which,*/bin/ps,'
+ '*/bin/grep,*/bin/java'),
]
]
+ get_default_valgrind_tool_specific_args())
# The default tool is Memcheck. Feeding these arguments to a different
# Valgrind tool will cause it to fail at startup, so don't do that!
def get_default_valgrind_tool_specific_args():
return ['--partial-loads-ok=yes',
'--leak-check=full',
'--show-possibly-lost=no',
]
]

View File

@ -8,9 +8,10 @@ PACKAGE_VERSION = '0.1'
setup(name='mozdebug',
version=PACKAGE_VERSION,
description="Utilities for running applications under native code debuggers intended for use in Mozilla testing",
description="Utilities for running applications under native code debuggers "
"intended for use in Mozilla testing",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Testing Team',
author_email='tools@lists.mozilla.org',
@ -24,4 +25,3 @@ setup(name='mozdebug',
# -*- Entry points: -*-
""",
)

View File

@ -10,7 +10,9 @@ import sys
from mozdevice import DeviceManagerADB
class TestFileOperations(unittest.TestCase):
def setUp(self):
dm = DeviceManagerADB()
dm.reboot(wait=True)
@ -34,13 +36,13 @@ class TestFileOperations(unittest.TestCase):
if __name__ == "__main__":
dm = DeviceManagerADB()
if not dm.devices():
print "There are no connected adb devices"
sys.exit(1)
print "There are no connected adb devices"
sys.exit(1)
else:
if not (int(dm._runCmd(["shell", "getprop", "ro.secure"]).output[0]) and \
if not (int(dm._runCmd(["shell", "getprop", "ro.secure"]).output[0]) and
int(dm._runCmd(["shell", "getprop", "ro.debuggable"]).output[0])):
print "This test case is meant for devices with devices that start " \
"adbd as non-root and allows for adbd to be restarted as root."
"adbd as non-root and allows for adbd to be restarted as root."
sys.exit(1)
unittest.main()

View File

@ -44,11 +44,13 @@ from StringIO import StringIO
from mozdevice import DeviceManagerADB, DMError
def find_mount_permissions(dm, mount_path):
for mount_point in dm._runCmd(["shell", "mount"]).output:
if mount_point.find(mount_path) > 0:
return re.search('(ro|rw)(?=,)', mount_point).group(0)
class DeviceManagerADBTestCase(unittest.TestCase):
tempLocalDir = "tempDir"
tempLocalFile = os.path.join(tempLocalDir, "tempfile.txt")
@ -81,7 +83,7 @@ class DeviceManagerADBTestCase(unittest.TestCase):
open(self.tempLocalFile, 'w').close()
self.tempRemoteDir = self.dm.getTempDir()
self.tempRemoteFile = os.path.join(self.tempRemoteDir,
os.path.basename(self.tempLocalFile))
os.path.basename(self.tempLocalFile))
self.tempRemoteSystemFile = \
os.path.join("/system", os.path.basename(self.tempLocalFile))
@ -97,6 +99,7 @@ class DeviceManagerADBTestCase(unittest.TestCase):
class TestFileOperations(DeviceManagerADBTestCase):
def test_make_and_remove_directory(self):
dir1 = os.path.join(self.tempRemoteDir, "dir1")
self.assertFalse(self.dm.dirExists(dir1))
@ -168,6 +171,7 @@ class TestFileOperations(DeviceManagerADBTestCase):
class TestOther(DeviceManagerADBTestCase):
def test_get_list_of_processes(self):
self.assertEquals(type(self.dm.getProcessList()), list)
@ -183,7 +187,7 @@ class TestOther(DeviceManagerADBTestCase):
def test_shell(self):
out = StringIO()
self.dm.shell(["echo", "$COMPANY", ";", "pwd"], out,
env={"COMPANY":"Mozilla"}, cwd="/", timeout=4, root=True)
env={"COMPANY": "Mozilla"}, cwd="/", timeout=4, root=True)
output = str(out.getvalue()).rstrip().splitlines()
out.close()
self.assertEquals(output, ['Mozilla', '/'])
@ -203,13 +207,13 @@ class TestOther(DeviceManagerADBTestCase):
if __name__ == '__main__':
dm = DeviceManagerADB()
if not dm.devices():
print "There are no connected adb devices"
sys.exit(1)
print "There are no connected adb devices"
sys.exit(1)
if find_mount_permissions(dm, "/system") == "rw":
print "We've found out that /system is mounted as 'rw'. This is because the command " \
"'adb remount' has been run before running this test case. Please reboot the device " \
"and try again."
"'adb remount' has been run before running this test case. Please reboot the device " \
"and try again."
sys.exit(1)
unittest.main()

View File

@ -9,3 +9,7 @@ from devicemanager import DeviceManager, DMError, ZeroconfListener
from devicemanagerADB import DeviceManagerADB
from devicemanagerSUT import DeviceManagerSUT
from droid import DroidADB, DroidSUT, DroidConnectByHWID
__all__ = ['ADBError', 'ADBRootError', 'ADBTimeoutError', 'ADBProcess', 'ADBCommand', 'ADBHost',
'ADBDevice', 'ADBAndroid', 'ADBB2G', 'DeviceManager', 'DMError', 'ZeroconfListener',
'DeviceManagerADB', 'DeviceManagerSUT', 'DroidADB', 'DroidSUT', 'DroidConnectByHWID']

View File

@ -15,6 +15,7 @@ from abc import ABCMeta, abstractmethod
class ADBProcess(object):
"""ADBProcess encapsulates the data related to executing the adb process."""
def __init__(self, args):
#: command argument argument list.
self.args = args
@ -59,6 +60,7 @@ class ADBProcess(object):
# differently in order that unhandled ADBRootErrors and
# ADBTimeoutErrors can be handled distinctly from ADBErrors.
class ADBError(Exception):
"""ADBError is raised in situations where a command executed on a
device either exited with a non-zero exitcode or when an
@ -67,16 +69,19 @@ class ADBError(Exception):
"""
pass
class ADBListDevicesError(ADBError):
"""ADBListDevicesError is raised when errors are found listing the
devices, typically not any permissions.
The devices information is stocked with the *devices* member.
"""
def __init__(self, msg, devices):
ADBError.__init__(self, msg)
self.devices = devices
class ADBRootError(Exception):
"""ADBRootError is raised when a shell command is to be executed as
root but the device does not support it. This error is fatal since
@ -85,6 +90,7 @@ class ADBRootError(Exception):
"""
pass
class ADBTimeoutError(Exception):
"""ADBTimeoutError is raised when either a host command or shell
command takes longer than the specified timeout to execute. The
@ -234,10 +240,10 @@ class ADBCommand(object):
start_time = time.time()
adb_process.exitcode = adb_process.proc.poll()
while ((time.time() - start_time) <= timeout and
adb_process.exitcode == None):
adb_process.exitcode is None):
time.sleep(self._polling_interval)
adb_process.exitcode = adb_process.proc.poll()
if adb_process.exitcode == None:
if adb_process.exitcode is None:
adb_process.proc.kill()
adb_process.timedout = True
adb_process.exitcode = adb_process.proc.poll()
@ -310,6 +316,7 @@ class ADBHost(ADBCommand):
adbhost = ADBHost()
adbhost.start_server()
"""
def __init__(self,
adb='adb',
adb_host=None,
@ -458,7 +465,9 @@ class ADBHost(ADBCommand):
"""
# b313b945 device usb:1-7 product:d2vzw model:SCH_I535 device:d2vzw
# from Android system/core/adb/transport.c statename()
re_device_info = re.compile(r'([^\s]+)\s+(offline|bootloader|device|host|recovery|sideload|no permissions|unauthorized|unknown)')
re_device_info = re.compile(
r"([^\s]+)\s+(offline|bootloader|device|host|recovery|sideload|"
"no permissions|unauthorized|unknown)")
devices = []
lines = self.command_output(["devices", "-l"], timeout=timeout).split('\n')
for line in lines:
@ -665,7 +674,6 @@ class ADBDevice(ADBCommand):
except ADBError:
self._logger.debug("Check for root adbd failed")
@staticmethod
def _escape_command_line(cmd):
"""Utility function to return escaped and quoted version of command
@ -1039,10 +1047,10 @@ class ADBDevice(ADBCommand):
start_time = time.time()
exitcode = adb_process.proc.poll()
while ((time.time() - start_time) <= timeout) and exitcode == None:
while ((time.time() - start_time) <= timeout) and exitcode is None:
time.sleep(self._polling_interval)
exitcode = adb_process.proc.poll()
if exitcode == None:
if exitcode is None:
adb_process.proc.kill()
adb_process.timedout = True
adb_process.exitcode = adb_process.proc.poll()
@ -1130,12 +1138,12 @@ class ADBDevice(ADBCommand):
'timedout: %s, '
'exitcode: %s, '
'output: %s' %
(' '.join(adb_process.args),
timeout,
(' '.join(adb_process.args),
timeout,
root,
adb_process.timedout,
adb_process.exitcode,
output))
adb_process.timedout,
adb_process.exitcode,
output))
return output
finally:
@ -1698,7 +1706,7 @@ class ADBDevice(ADBCommand):
if not pid_list:
break
self._logger.debug("Attempt %d of %d to kill processes %s failed" %
(attempt+1, attempts, pid_list))
(attempt + 1, attempts, pid_list))
time.sleep(wait)
if pid_list:

View File

@ -153,7 +153,7 @@ class ADBAndroid(ADBDevice):
elif parameter == 'scale':
scale = float(value)
if parameter is not None and scale is not None:
percentage = 100.0*level/scale
percentage = 100.0 * level / scale
break
return percentage
@ -219,7 +219,7 @@ class ADBAndroid(ADBDevice):
if not success:
self._logger.debug('Attempt %s of %s device not ready: %s' % (
attempt+1, self._device_ready_retry_attempts,
attempt + 1, self._device_ready_retry_attempts,
failure))
time.sleep(self._device_ready_retry_wait)
@ -293,8 +293,8 @@ class ADBAndroid(ADBDevice):
return True
def launch_application(self, app_name, activity_name, intent, url=None,
extras=None, wait=True, fail_if_running=True,
timeout=None):
extras=None, wait=True, fail_if_running=True,
timeout=None):
"""Launches an Android application
:param str app_name: Name of application (e.g. `com.android.chrome`)
@ -326,7 +326,7 @@ class ADBAndroid(ADBDevice):
raise ADBError("Only one instance of an application may be running "
"at once")
acmd = [ "am", "start" ] + \
acmd = ["am", "start"] + \
["-W" if wait else '', "-n", "%s/%s" % (app_name, activity_name)]
if intent:
@ -349,8 +349,8 @@ class ADBAndroid(ADBDevice):
self.shell_output(cmd, timeout=timeout)
def launch_fennec(self, app_name, intent="android.intent.action.VIEW",
moz_env=None, extra_args=None, url=None, wait=True,
fail_if_running=True, timeout=None):
moz_env=None, extra_args=None, url=None, wait=True,
fail_if_running=True, timeout=None):
"""Convenience method to launch Fennec on Android with various
debugging arguments
@ -391,9 +391,10 @@ class ADBAndroid(ADBDevice):
if extra_args:
extras['args'] = " ".join(extra_args)
self.launch_application(app_name, "org.mozilla.gecko.BrowserApp", intent, url=url, extras=extras,
wait=wait, fail_if_running=fail_if_running,
timeout=timeout)
self.launch_application(app_name, "org.mozilla.gecko.BrowserApp", intent, url=url,
extras=extras,
wait=wait, fail_if_running=fail_if_running,
timeout=timeout)
def stop_application(self, app_name, timeout=None, root=False):
"""Stops the specified application
@ -428,7 +429,7 @@ class ADBAndroid(ADBDevice):
while self.process_exist(app_name, timeout=timeout):
if num_tries > max_tries:
raise ADBError("Couldn't successfully kill %s after %s "
"tries" % (app_name, max_tries))
"tries" % (app_name, max_tries))
self.pkill(app_name, timeout=timeout, root=root)
num_tries += 1

View File

@ -14,26 +14,30 @@ import zlib
from functools import wraps
class DMError(Exception):
"generic devicemanager exception."
def __init__(self, msg= '', fatal = False):
def __init__(self, msg='', fatal=False):
self.msg = msg
self.fatal = fatal
def __str__(self):
return self.msg
def abstractmethod(method):
line = method.func_code.co_firstlineno
filename = method.func_code.co_filename
@wraps(method)
def not_implemented(*args, **kwargs):
raise NotImplementedError('Abstract method %s at File "%s", line %s '
'should be implemented by a concrete class' %
(repr(method), filename, line))
'should be implemented by a concrete class' %
(repr(method), filename, line))
return not_implemented
class DeviceManager(object):
"""
Represents a connection to a device. Once an implementation of this class
@ -52,7 +56,7 @@ class DeviceManager(object):
def __init__(self, logLevel=None, deviceRoot=None):
try:
self._logger = mozlog.get_default_logger(component="mozdevice")
if not self._logger: # no global structured logger, fall back to reg logging
if not self._logger: # no global structured logger, fall back to reg logging
self._logger = mozlog.unstructured.getLogger("mozdevice")
if logLevel is not None:
self._logger.setLevel(logLevel)
@ -96,7 +100,7 @@ class DeviceManager(object):
@debug.setter
def debug_setter(self, newDebug):
self._logger.warning("dm.debug is deprecated. Use logLevel.")
newDebug = 5 if newDebug > 5 else newDebug # truncate >=5 to 5
newDebug = 5 if newDebug > 5 else newDebug # truncate >=5 to 5
levels = {5: logging.DEBUG, 3: logging.INFO, 2: logging.WARNING,
1: logging.ERROR, 0: logging.CRITICAL}
self.logLevel = levels[newDebug]
@ -111,7 +115,8 @@ class DeviceManager(object):
- `os` - name of the os
- `id` - unique id of the device
- `uptime` - uptime of the device
- `uptimemillis` - uptime of the device in milliseconds (NOT supported on all implementations)
- `uptimemillis` - uptime of the device in milliseconds
(NOT supported on all implementations)
- `systime` - system time of the device
- `screen` - screen resolution
- `memory` - memory stats
@ -137,7 +142,7 @@ class DeviceManager(object):
for interface in interfaces:
match = re.match(r"%s: ip (\S+)" % interface,
self.shellCheckOutput(['ifconfig', interface],
timeout=self.short_timeout))
timeout=self.short_timeout))
if match:
return match.group(1)
@ -145,15 +150,17 @@ class DeviceManager(object):
"""
Clears the logcat file making it easier to view specific events.
"""
#TODO: spawn this off in a separate thread/process so we can collect all the logcat information
# TODO: spawn this off in a separate thread/process so we can collect all
# the logcat information
# Right now this is just clearing the logcat so we can only see what happens after this call.
# Right now this is just clearing the logcat so we can only see what
# happens after this call.
self.shellCheckOutput(['/system/bin/logcat', '-c'], root=self._logcatNeedsRoot,
timeout=self.short_timeout)
def getLogcat(self, filterSpecs=["dalvikvm:I", "ConnectivityService:S",
"WifiMonitor:S", "WifiStateTracker:S",
"wpa_supplicant:S", "NetworkStateTracker:S"],
"WifiMonitor:S", "WifiStateTracker:S",
"wpa_supplicant:S", "NetworkStateTracker:S"],
format="time",
filterOutRegexps=[]):
"""
@ -162,8 +169,8 @@ class DeviceManager(object):
"""
cmdline = ["/system/bin/logcat", "-v", format, "-d"] + filterSpecs
output = self.shellCheckOutput(cmdline,
root=self._logcatNeedsRoot,
timeout=self.short_timeout)
root=self._logcatNeedsRoot,
timeout=self.short_timeout)
lines = output.replace('\r\n', '\n').splitlines(True)
for regex in filterOutRegexps:
@ -252,8 +259,8 @@ class DeviceManager(object):
if (parts[1] == ""):
remoteRoot = remoteDirname
remoteName = remoteRoot + '/' + f
if (self.validateFile(remoteName, os.path.join(root, f)) <> True):
return False
if (self.validateFile(remoteName, os.path.join(root, f)) is not True):
return False
return True
@abstractmethod
@ -277,7 +284,7 @@ class DeviceManager(object):
for part in parts[:-1]:
if part != "":
name = posixpath.join(name, part)
self.mkDir(name) # mkDir will check previous existence
self.mkDir(name) # mkDir will check previous existence
@abstractmethod
def dirExists(self, dirpath):
@ -314,21 +321,21 @@ class DeviceManager(object):
@abstractmethod
def moveTree(self, source, destination):
"""
Does a move of the file or directory on the device.
"""
Does a move of the file or directory on the device.
:param source: Path to the original file or directory
:param destination: Path to the destination file or directory
"""
:param source: Path to the original file or directory
:param destination: Path to the destination file or directory
"""
@abstractmethod
def copyTree(self, source, destination):
"""
Does a copy of the file or directory on the device.
"""
Does a copy of the file or directory on the device.
:param source: Path to the original file or directory
:param destination: Path to the destination file or directory
"""
:param source: Path to the original file or directory
:param destination: Path to the destination file or directory
"""
@abstractmethod
def chmodDir(self, remoteDirname, mask="777"):
@ -402,7 +409,9 @@ class DeviceManager(object):
output = str(buf.getvalue()[0:-1]).rstrip()
buf.close()
if retval != 0:
raise DMError("Non-zero return code for command: %s (output: '%s', retval: '%s')" % (cmd, output, retval))
raise DMError(
"Non-zero return code for command: %s "
"(output: '%s', retval: '%s')" % (cmd, output, retval))
return output
@abstractmethod
@ -424,12 +433,12 @@ class DeviceManager(object):
processInfo = None
#filter out extra spaces
# filter out extra spaces
parts = filter(lambda x: x != '', processName.split(' '))
processName = ' '.join(parts)
#filter out the quoted env string if it exists
#ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
# filter out the quoted env string if it exists
# ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
parts = processName.split('"')
if (len(parts) > 2):
processName = ' '.join(parts[2:]).strip()
@ -485,7 +494,8 @@ class DeviceManager(object):
Installs an application onto the device.
:param appBundlePath: path to the application bundle on the device
:param destPath: destination directory of where application should be installed to (optional)
:param destPath: destination directory of where application should be
installed to (optional)
"""
@abstractmethod
@ -531,15 +541,19 @@ class DeviceManager(object):
"""
# Based on: http://code.activestate.com/recipes/577443-write-a-png-image-in-native-python/
width_byte_4 = width * 4
raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4] for span in range(0, (height - 1) * width * 4, width_byte_4))
raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4]
for span in range(0, (height - 1) * width * 4, width_byte_4))
def png_pack(png_tag, data):
chunk_head = png_tag + data
return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
return struct.pack("!I", len(data)) \
+ chunk_head \
+ struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
return b"".join([
b'\x89PNG\r\n\x1a\n',
png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
png_pack(b'IDAT', zlib.compress(raw_data, 9)),
png_pack(b'IEND', b'')])
b'\x89PNG\r\n\x1a\n',
png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
png_pack(b'IDAT', zlib.compress(raw_data, 9)),
png_pack(b'IEND', b'')])
@abstractmethod
def _getRemoteHash(self, filename):
@ -553,7 +567,7 @@ class DeviceManager(object):
Return the MD5 sum of a file on the host.
"""
f = open(filename, 'rb')
if (f == None):
if f is None:
return None
try:
@ -582,7 +596,7 @@ class DeviceManager(object):
arg.replace('&', '\&')
needsQuoting = False
for char in [ ' ', '(', ')', '"', '&' ]:
for char in [' ', '(', ')', '"', '&']:
if arg.find(char) >= 0:
needsQuoting = True
break
@ -593,6 +607,7 @@ class DeviceManager(object):
return " ".join(quotedCmd)
def _pop_last_line(file_obj):
"""
Utility function to get the last line from a file (shared between ADB and
@ -603,20 +618,20 @@ def _pop_last_line(file_obj):
file_obj.seek(0, 2)
length = file_obj.tell() + 1
while bytes_from_end < length:
file_obj.seek((-1)*bytes_from_end, 2)
file_obj.seek((-1) * bytes_from_end, 2)
data = file_obj.read()
if bytes_from_end == length-1 and len(data) == 0: # no data, return None
if bytes_from_end == length - 1 and len(data) == 0: # no data, return None
return None
if data[0] == '\n' or bytes_from_end == length-1:
if data[0] == '\n' or bytes_from_end == length - 1:
# found the last line, which should have the return value
if data[0] == '\n':
data = data[1:]
# truncate off the return code line
file_obj.truncate(length - bytes_from_end)
file_obj.seek(0,2)
file_obj.seek(0, 2)
file_obj.write('\0')
return data
@ -625,14 +640,16 @@ def _pop_last_line(file_obj):
return None
class ZeroconfListener(object):
def __init__(self, hwid, evt):
self.hwid = hwid
self.evt = evt
# Format is 'SUTAgent [hwid:015d2bc2825ff206] [ip:10_242_29_221]._sutagent._tcp.local.'
def addService(self, zeroconf, type, name):
#print "Found _sutagent service broadcast:", name
# print "Found _sutagent service broadcast:", name
if not name.startswith("SUTAgent"):
return

View File

@ -131,7 +131,7 @@ class DeviceManagerADB(DeviceManager):
cmdline = envstr + "; " + cmdline
# all output should be in stdout
args=[self._adbPath]
args = [self._adbPath]
if self._serverHost is not None:
args.extend(['-H', self._serverHost])
if self._serverPort is not None:
@ -147,7 +147,8 @@ class DeviceManagerADB(DeviceManager):
proc = ProcessHandler(args, processOutputLine=self._log, onTimeout=_timeout)
if not timeout:
# We are asserting that all commands will complete in this time unless otherwise specified
# We are asserting that all commands will complete in this time unless
# otherwise specified
timeout = self.default_timeout
timeout = int(timeout)
@ -164,7 +165,7 @@ class DeviceManagerADB(DeviceManager):
for line in output:
outputfile.write(line + '\n')
outputfile.seek(-2, 2)
outputfile.truncate() # truncate off the return code
outputfile.truncate() # truncate off the return code
return int(return_code)
return None
@ -196,7 +197,6 @@ class DeviceManagerADB(DeviceManager):
if not self._checkCmd(cmd, timeout=self.short_timeout) == 0:
raise DMError("Failed to remove connection forwarding.")
def remount(self):
"Remounts the /system partition on the device read-write."
return self._checkCmd(['remount'], timeout=self.short_timeout)
@ -204,7 +204,7 @@ class DeviceManagerADB(DeviceManager):
def devices(self):
"Return a list of connected devices as (serial, status) tuples."
proc = self._runCmd(['devices'])
proc.output.pop(0) # ignore first line of output
proc.output.pop(0) # ignore first line of output
devices = []
for line in proc.output:
result = re.match('(.*?)\t(.*)', line)
@ -231,9 +231,10 @@ class DeviceManagerADB(DeviceManager):
raise DMError("File not found: %s" % localname)
proc = self._runCmd(["push", os.path.realpath(localname), destname],
retryLimit=retryLimit)
retryLimit=retryLimit)
if proc.returncode != 0:
raise DMError("Error pushing file %s -> %s; output: %s" % (localname, destname, proc.output))
raise DMError("Error pushing file %s -> %s; output: %s" %
(localname, destname, proc.output))
def mkDir(self, name):
result = self._runCmd(["shell", "mkdir", name], timeout=self.short_timeout).output
@ -248,12 +249,12 @@ class DeviceManagerADB(DeviceManager):
retryLimit = retryLimit or self.retryLimit
if self._useZip:
self.removeDir(remoteDir)
self.mkDirs(remoteDir+"/x")
self.mkDirs(remoteDir + "/x")
try:
localZip = tempfile.mktemp() + ".zip"
remoteZip = remoteDir + "/adbdmtmp.zip"
proc = ProcessHandler(["zip", "-r", localZip, '.'], cwd=localDir,
processOutputLine=self._log)
processOutputLine=self._log)
proc.run()
proc.wait()
self.pushFile(localZip, remoteZip, retryLimit=retryLimit, createDir=False)
@ -273,7 +274,7 @@ class DeviceManagerADB(DeviceManager):
# If the remote directory exists, newer implementations of
# "adb push" will create a sub-directory, while older versions
# will not! Bug 1285040
self.mkDirs(remoteDir+"/x")
self.mkDirs(remoteDir + "/x")
self.removeDir(remoteDir)
tmpDir = tempfile.mkdtemp()
# copytree's target dir must not already exist, so create a subdir
@ -365,13 +366,13 @@ class DeviceManagerADB(DeviceManager):
DEPRECATED: Use shell() or launchApplication() for new code
"""
#strip out env vars
parts = appname.split('"');
# strip out env vars
parts = appname.split('"')
if (len(parts) > 2):
parts = parts[2:]
return self.launchProcess(parts, failIfRunning)
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
def launchProcess(self, cmd, outputFile="process.txt", cwd='', env='', failIfRunning=False):
"""
Launches a process, redirecting output to standard out
@ -403,13 +404,13 @@ class DeviceManagerADB(DeviceManager):
acmd.append("--es")
acmd.append("args")
acmd.append(args)
if env != '' and env != None:
if env != '' and env is not None:
envCnt = 0
# env is expected to be a dict of environment variables
for envkey, envval in env.iteritems():
acmd.append("--es")
acmd.append("env" + str(envCnt))
acmd.append(envkey + "=" + envval);
acmd.append(envkey + "=" + envval)
envCnt += 1
if uri != "":
acmd.append("-d")
@ -504,7 +505,7 @@ class DeviceManagerADB(DeviceManager):
pass
raise DMError("Unable to set up device root using paths: [%s]"
% ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
% ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
def getTempDir(self):
# Cache result to speed up operations depending
@ -515,7 +516,7 @@ class DeviceManagerADB(DeviceManager):
return self._tempDir
def reboot(self, wait = False, **kwargs):
def reboot(self, wait=False, **kwargs):
self._checkCmd(["reboot"])
if wait:
self._checkCmd(["wait-for-device"])
@ -530,7 +531,7 @@ class DeviceManagerADB(DeviceManager):
timestr = str(self._runCmd(["shell", "date", "+%s"], timeout=self.short_timeout).output[0])
if (not timestr or not timestr.isdigit()):
raise DMError("Unable to get current time using date (got: '%s')" % timestr)
return int(timestr)*1000
return int(timestr) * 1000
def getInfo(self, directive=None):
directive = directive or "all"
@ -538,7 +539,8 @@ class DeviceManagerADB(DeviceManager):
if directive == "id" or directive == "all":
ret["id"] = self._runCmd(["get-serialno"], timeout=self.short_timeout).output[0]
if directive == "os" or directive == "all":
ret["os"] = self.shellCheckOutput(["getprop", "ro.build.display.id"], timeout=self.short_timeout)
ret["os"] = self.shellCheckOutput(
["getprop", "ro.build.display.id"], timeout=self.short_timeout)
if directive == "uptime" or directive == "all":
uptime = self.shellCheckOutput(["uptime"], timeout=self.short_timeout)
if not uptime:
@ -560,7 +562,8 @@ class DeviceManagerADB(DeviceManager):
meminfo[key] = value.strip()
ret["memtotal"] = meminfo["MemTotal"]
if directive == "disk" or directive == "all":
data = self.shellCheckOutput(["df", "/data", "/system", "/sdcard"], timeout=self.short_timeout)
data = self.shellCheckOutput(
["df", "/data", "/system", "/sdcard"], timeout=self.short_timeout)
ret["disk"] = data.split('\n')
self._logger.debug("getInfo: %s" % ret)
return ret
@ -600,10 +603,10 @@ class DeviceManagerADB(DeviceManager):
retries = 0
while retries < retryLimit:
proc = ProcessHandler(finalArgs, storeOutput=True,
processOutputLine=self._log, onTimeout=_timeout)
processOutputLine=self._log, onTimeout=_timeout)
proc.run(timeout=timeout)
proc.returncode = proc.wait()
if proc.returncode == None:
if proc.returncode is None:
proc.kill()
retries += 1
else:
@ -642,7 +645,7 @@ class DeviceManagerADB(DeviceManager):
proc = ProcessHandler(finalArgs, processOutputLine=self._log, onTimeout=_timeout)
proc.run(timeout=timeout)
ret_code = proc.wait()
if ret_code == None:
if ret_code is None:
proc.kill()
retries += 1
else:
@ -661,7 +664,8 @@ class DeviceManagerADB(DeviceManager):
if (self.dirExists(remoteEntry)):
self.chmodDir(remoteEntry)
else:
self._checkCmd(["shell", "chmod", mask, remoteEntry], timeout=self.short_timeout)
self._checkCmd(["shell", "chmod", mask, remoteEntry],
timeout=self.short_timeout)
self._logger.info("chmod %s" % remoteEntry)
self._checkCmd(["shell", "chmod", mask, remoteDir], timeout=self.short_timeout)
self._logger.debug("chmod %s" % remoteDir)
@ -680,7 +684,9 @@ class DeviceManagerADB(DeviceManager):
try:
self._checkCmd(["version"], timeout=self.short_timeout)
except os.error as err:
raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
raise DMError(
"unable to execute ADB (%s): ensure Android SDK is installed "
"and adb is in your $PATH" % err)
def _verifyDevice(self):
# If there is a device serial number, see if adb is connected to it
@ -691,7 +697,7 @@ class DeviceManagerADB(DeviceManager):
if m:
if self._deviceSerial == m.group(1):
deviceStatus = m.group(2)
if deviceStatus == None:
if deviceStatus is None:
raise DMError("device not found: %s" % self._deviceSerial)
elif deviceStatus != "device":
raise DMError("bad status for device %s: %s" % (self._deviceSerial, deviceStatus))
@ -730,7 +736,7 @@ class DeviceManagerADB(DeviceManager):
retcode = None
while (time.time() - start_time) <= 15 and retcode is None:
retcode = proc.poll()
if retcode is None: # still not terminated, kill
if retcode is None: # still not terminated, kill
proc.kill()
if proc.output and 'uid=0(root)' in proc.output[0]:
@ -789,7 +795,7 @@ class DeviceManagerADB(DeviceManager):
# Check if busybox -1A is required in order to get one
# file per line.
output = self._runCmd(["shell", "ls", "-1A", "/"],
timeout=self.short_timeout).output
timeout=self.short_timeout).output
output = ' '.join(output)
if 'error: device not found' in output:
raise DMError(output)
@ -799,4 +805,3 @@ class DeviceManagerADB(DeviceManager):
self._lsModifier = "-a"
else:
self._lsModifier = "-1A"

View File

@ -17,6 +17,7 @@ from devicemanager import DeviceManager, DMError, _pop_last_line
import errno
from distutils.version import StrictVersion
class DeviceManagerSUT(DeviceManager):
"""
Implementation of DeviceManager interface that speaks to a device over
@ -45,7 +46,7 @@ class DeviceManagerSUT(DeviceManager):
self._everConnected = False
# Get version
verstring = self._runCmds([{ 'cmd': 'ver' }])
verstring = self._runCmds([{'cmd': 'ver'}])
ver_re = re.match('(\S+) Version (\S+)', verstring)
self.agentProductName = ver_re.group(1)
self.agentVersion = ver_re.group(2)
@ -109,7 +110,7 @@ class DeviceManagerSUT(DeviceManager):
return True
return False
def _sendCmds(self, cmdlist, outputfile, timeout = None, retryLimit = None):
def _sendCmds(self, cmdlist, outputfile, timeout=None, retryLimit=None):
"""
Wrapper for _doCmds that loops up to retryLimit iterations
"""
@ -138,9 +139,10 @@ class DeviceManagerSUT(DeviceManager):
self._logger.info('Could not connect; sleeping for %d seconds.' % sleep_time)
time.sleep(sleep_time)
raise DMError("Remote Device Error: unable to connect to %s after %s attempts" % (self.host, retryLimit))
raise DMError("Remote Device Error: unable to connect to %s after %s attempts" %
(self.host, retryLimit))
def _runCmds(self, cmdlist, timeout = None, retryLimit = None):
def _runCmds(self, cmdlist, timeout=None, retryLimit=None):
"""
Similar to _sendCmds, but just returns any output as a string instead of
writing to a file
@ -156,7 +158,8 @@ class DeviceManagerSUT(DeviceManager):
shouldCloseSocket = False
if not timeout:
# We are asserting that all commands will complete in this time unless otherwise specified
# We are asserting that all commands will complete in this time unless
# otherwise specified
timeout = self.default_timeout
if not self._sock:
@ -166,7 +169,7 @@ class DeviceManagerSUT(DeviceManager):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as msg:
self._sock = None
raise DMError("Automation Error: unable to create socket: "+str(msg))
raise DMError("Automation Error: unable to create socket: " + str(msg))
try:
self._sock.settimeout(float(timeout))
@ -174,7 +177,7 @@ class DeviceManagerSUT(DeviceManager):
self._everConnected = True
except socket.error as msg:
self._sock = None
raise DMError("Remote Device Error: Unable to connect socket: "+str(msg))
raise DMError("Remote Device Error: Unable to connect socket: " + str(msg))
# consume prompt
try:
@ -182,7 +185,9 @@ class DeviceManagerSUT(DeviceManager):
except socket.error as msg:
self._sock.close()
self._sock = None
raise DMError("Remote Device Error: Did not get prompt after connecting: " + str(msg), fatal=True)
raise DMError(
"Remote Device Error: Did not get prompt after connecting: " + str(msg),
fatal=True)
# future recv() timeouts are handled by select() calls
self._sock.settimeout(None)
@ -208,8 +213,8 @@ class DeviceManagerSUT(DeviceManager):
except socket.error as msg:
self._sock.close()
self._sock = None
self._logger.error("Remote Device Error: Error sending data"\
" to socket. cmd=%s; err=%s" % (cmd['cmd'], msg))
self._logger.error("Remote Device Error: Error sending data"
" to socket. cmd=%s; err=%s" % (cmd['cmd'], msg))
return False
# Check if the command should close the socket
@ -243,7 +248,8 @@ class DeviceManagerSUT(DeviceManager):
if timer > timeout:
self._sock.close()
self._sock = None
raise DMError("Automation Error: Timeout in command %s" % cmd['cmd'], fatal=True)
raise DMError("Automation Error: Timeout in command %s" %
cmd['cmd'], fatal=True)
except socket.error as err:
socketClosed = True
errStr = str(err)
@ -254,7 +260,9 @@ class DeviceManagerSUT(DeviceManager):
if socketClosed:
self._sock.close()
self._sock = None
raise DMError("Automation Error: Error receiving data from socket. cmd=%s; err=%s" % (cmd, errStr))
raise DMError(
"Automation Error: Error receiving data from socket. "
"cmd=%s; err=%s" % (cmd, errStr))
data += temp
@ -276,8 +284,8 @@ class DeviceManagerSUT(DeviceManager):
# periodically flush data to output file to make sure it doesn't get
# too big/unwieldly
if len(data) > 1024:
outputfile.write(data[0:1024])
data = data[1024:]
outputfile.write(data[0:1024])
data = data[1024:]
if commandFailed:
raise DMError("Automation Error: Error processing command '%s'; err='%s'" %
@ -297,7 +305,7 @@ class DeviceManagerSUT(DeviceManager):
def _setupDeviceRoot(self, deviceRoot):
if not deviceRoot:
deviceRoot = "%s/tests" % self._runCmds(
[{ 'cmd': 'testroot' }]).strip()
[{'cmd': 'testroot'}]).strip()
self.mkDir(deviceRoot)
return deviceRoot
@ -327,16 +335,16 @@ class DeviceManagerSUT(DeviceManager):
cmd += "su"
if cwd:
self._sendCmds([{ 'cmd': '%s %s %s' % (cmd, cwd, cmdline) }], outputfile, timeout)
self._sendCmds([{'cmd': '%s %s %s' % (cmd, cwd, cmdline)}], outputfile, timeout)
else:
if (not root) or haveExecSu:
self._sendCmds([{ 'cmd': '%s %s' % (cmd, cmdline) }], outputfile, timeout)
self._sendCmds([{'cmd': '%s %s' % (cmd, cmdline)}], outputfile, timeout)
else:
# need to manually inject su -c for backwards compatibility (this may
# not work on ICS or above!!)
# (FIXME: this backwards compatibility code is really ugly and should
# be deprecated at some point in the future)
self._sendCmds([ { 'cmd': '%s su -c "%s"' % (cmd, cmdline) }], outputfile,
self._sendCmds([{'cmd': '%s su -c "%s"' % (cmd, cmdline)}], outputfile,
timeout)
# dig through the output to get the return code
@ -347,7 +355,8 @@ class DeviceManagerSUT(DeviceManager):
return int(m.group(1))
# woops, we couldn't find an end of line/return value
raise DMError("Automation Error: Error finding end of line/return value when running '%s'" % cmdline)
raise DMError(
"Automation Error: Error finding end of line/return value when running '%s'" % cmdline)
def pushFile(self, localname, destname, retryLimit=None, createDir=True):
retryLimit = retryLimit or self.retryLimit
@ -357,8 +366,8 @@ class DeviceManagerSUT(DeviceManager):
try:
filesize = os.path.getsize(localname)
with open(localname, 'rb') as f:
remoteHash = self._runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize),
'data': f.read() }], retryLimit=retryLimit).strip()
remoteHash = self._runCmds([{'cmd': 'push ' + destname + ' ' + str(filesize),
'data': f.read()}], retryLimit=retryLimit).strip()
except OSError:
raise DMError("DeviceManager: Error reading file to push")
@ -372,7 +381,7 @@ class DeviceManagerSUT(DeviceManager):
def mkDir(self, name):
if not self.dirExists(name):
self._runCmds([{ 'cmd': 'mkdr ' + name }])
self._runCmds([{'cmd': 'mkdr ' + name}])
def pushDir(self, localDir, remoteDir, retryLimit=None, timeout=None):
retryLimit = retryLimit or self.retryLimit
@ -394,10 +403,11 @@ class DeviceManagerSUT(DeviceManager):
self.mkDirs(remoteName)
existentDirectories.append(parent)
self.pushFile(os.path.join(root, f), remoteName, retryLimit=retryLimit, createDir=False)
self.pushFile(os.path.join(root, f), remoteName,
retryLimit=retryLimit, createDir=False)
def dirExists(self, remotePath):
ret = self._runCmds([{ 'cmd': 'isdir ' + remotePath }]).strip()
ret = self._runCmds([{'cmd': 'isdir ' + remotePath}]).strip()
if not ret:
raise DMError('Automation Error: DeviceManager isdir returned null')
@ -418,7 +428,7 @@ class DeviceManagerSUT(DeviceManager):
rootdir = posixpath.normpath(rootdir)
if not self.dirExists(rootdir):
return []
data = self._runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }])
data = self._runCmds([{'cmd': 'cd ' + rootdir}, {'cmd': 'ls'}])
files = filter(lambda x: x, data.splitlines())
if len(files) == 1 and files[0] == '<empty>':
@ -430,20 +440,20 @@ class DeviceManagerSUT(DeviceManager):
def removeFile(self, filename):
self._logger.info("removing file: " + filename)
if self.fileExists(filename):
self._runCmds([{ 'cmd': 'rm ' + filename }])
self._runCmds([{'cmd': 'rm ' + filename}])
def removeDir(self, remoteDir):
if self.dirExists(remoteDir):
self._runCmds([{ 'cmd': 'rmdr ' + remoteDir }])
self._runCmds([{'cmd': 'rmdr ' + remoteDir}])
def moveTree(self, source, destination):
self._runCmds([{ 'cmd': 'mv %s %s' % (source, destination) }])
self._runCmds([{'cmd': 'mv %s %s' % (source, destination)}])
def copyTree(self, source, destination):
self._runCmds([{ 'cmd': 'dd if=%s of=%s' % (source, destination) }])
self._runCmds([{'cmd': 'dd if=%s of=%s' % (source, destination)}])
def getProcessList(self):
data = self._runCmds([{ 'cmd': 'ps' }])
data = self._runCmds([{'cmd': 'ps'}])
processTuples = []
for line in data.splitlines():
@ -478,12 +488,12 @@ class DeviceManagerSUT(DeviceManager):
self._logger.info("FIRE PROC: '%s'" % appname)
if (self.processExist(appname) != None):
if (self.processExist(appname) is None):
self._logger.warning("process %s appears to be running already\n" % appname)
if (failIfRunning):
raise DMError("Automation Error: Process is already running")
self._runCmds([{ 'cmd': 'exec ' + appname }])
self._runCmds([{'cmd': 'exec ' + appname}])
# The 'exec' command may wait for the process to start and end, so checking
# for the process here may result in process = None.
@ -501,7 +511,7 @@ class DeviceManagerSUT(DeviceManager):
self._logger.debug("got pid: %s for process: %s" % (pid, appname))
return pid
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
def launchProcess(self, cmd, outputFile="process.txt", cwd='', env='', failIfRunning=False):
"""
Launches a process, redirecting output to standard out
@ -545,32 +555,32 @@ class DeviceManagerSUT(DeviceManager):
if pid and pid > 0:
try:
self.shellCheckOutput(['kill', '-%d' % sig, str(pid)],
root=True)
root=True)
except DMError as err:
self._logger.warning("unable to kill -%d %s (pid %s)" %
(sig, appname, str(pid)))
(sig, appname, str(pid)))
self._logger.debug(err)
raise err
else:
self._logger.warning("unable to kill -%d %s -- not running?" %
(sig, appname))
(sig, appname))
else:
retries = 0
while retries < self.retryLimit:
try:
if self.processExist(appname):
self._runCmds([{ 'cmd': 'kill ' + appname }])
self._runCmds([{'cmd': 'kill ' + appname}])
return
except DMError as err:
retries += 1
self._logger.warning("try %d of %d failed to kill %s" %
(retries, self.retryLimit, appname))
(retries, self.retryLimit, appname))
self._logger.debug(err)
if retries >= self.retryLimit:
raise err
def getTempDir(self):
return self._runCmds([{ 'cmd': 'tmpd' }]).strip()
return self._runCmds([{'cmd': 'tmpd'}]).strip()
def pullFile(self, remoteFile, offset=None, length=None):
# The "pull" command is different from other commands in that DeviceManager
@ -605,7 +615,7 @@ class DeviceManagerSUT(DeviceManager):
def read_until_char(c, buf, error_msg):
""" read until 'c' is found; buffer rest """
while not c in buf:
while c not in buf:
data = uread(1024, error_msg)
buf += data
return buf.partition(c)
@ -631,10 +641,10 @@ class DeviceManagerSUT(DeviceManager):
cmd = 'pull %s %d %d' % (remoteFile, offset, length)
elif offset is not None:
cmd = 'pull %s %d' % (remoteFile, offset)
else:
else:
cmd = 'pull %s' % remoteFile
self._runCmds([{ 'cmd': cmd }])
self._runCmds([{'cmd': cmd}])
# read metadata; buffer the rest
metadata, sep, buf = read_until_char('\n', buf, 'could not find metadata')
@ -658,7 +668,8 @@ class DeviceManagerSUT(DeviceManager):
# prompt should follow
read_exact(len(prompt), buf, 'could not find prompt')
# failures are expected, so don't use "Remote Device Error" or we'll RETRY
raise DMError("DeviceManager: pulling file '%s' unsuccessful: %s" % (remoteFile, error_str))
raise DMError("DeviceManager: pulling file '%s' unsuccessful: %s" %
(remoteFile, error_str))
# read file data
total_to_recv = filesize + len(prompt)
@ -703,7 +714,7 @@ class DeviceManagerSUT(DeviceManager):
remoteHash = self._getRemoteHash(remoteFile)
localHash = self._getLocalHash(localFile)
if (remoteHash == None):
if (remoteHash is None):
return False
if (remoteHash == localHash):
@ -712,7 +723,7 @@ class DeviceManagerSUT(DeviceManager):
return False
def _getRemoteHash(self, filename):
data = self._runCmds([{ 'cmd': 'hash ' + filename }]).strip()
data = self._runCmds([{'cmd': 'hash ' + filename}]).strip()
self._logger.debug("remote hash returned: '%s'" % data)
return data
@ -729,7 +740,7 @@ class DeviceManagerSUT(DeviceManager):
if destDir[-1] != '/':
destDir += '/'
self._runCmds([{ 'cmd': 'unzp %s %s' % (filePath, destDir)}])
self._runCmds([{'cmd': 'unzp %s %s' % (filePath, destDir)}])
def _getRebootServerSocket(self, ipAddr):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -770,7 +781,6 @@ class DeviceManagerSUT(DeviceManager):
"to 'settle'" % self.reboot_settling_time)
time.sleep(self.reboot_settling_time)
def reboot(self, ipAddr=None, port=30000, wait=False):
# port ^^^ is here for backwards compatibility only, we now
# determine a port automatically and safely
@ -809,14 +819,14 @@ class DeviceManagerSUT(DeviceManager):
result = {}
collapseSpaces = re.compile(' +')
directives = ['os','id','uptime','uptimemillis','systime','screen',
'rotation','memory','process','disk','power','sutuserinfo',
directives = ['os', 'id', 'uptime', 'uptimemillis', 'systime', 'screen',
'rotation', 'memory', 'process', 'disk', 'power', 'sutuserinfo',
'temperature']
if (directive in directives):
directives = [directive]
for d in directives:
data = self._runCmds([{ 'cmd': 'info ' + d }])
data = self._runCmds([{'cmd': 'info ' + d}])
data = collapseSpaces.sub(' ', data)
result[d] = data.split('\n')
@ -841,7 +851,7 @@ class DeviceManagerSUT(DeviceManager):
if destPath:
cmd += ' ' + destPath
data = self._runCmds([{ 'cmd': cmd }])
data = self._runCmds([{'cmd': cmd}])
if 'installation complete [0]' not in data:
raise DMError("Remove Device Error: Error installing app. Error message: %s" % data)
@ -850,7 +860,7 @@ class DeviceManagerSUT(DeviceManager):
cmd = 'uninstall ' + appName
if installPath:
cmd += ' ' + installPath
data = self._runCmds([{ 'cmd': cmd }])
data = self._runCmds([{'cmd': cmd}])
status = data.split('\n')[0].strip()
self._logger.debug("uninstallApp: '%s'" % status)
@ -862,7 +872,7 @@ class DeviceManagerSUT(DeviceManager):
cmd = 'uninst ' + appName
if installPath:
cmd += ' ' + installPath
data = self._runCmds([{ 'cmd': cmd }])
data = self._runCmds([{'cmd': cmd}])
self._logger.debug("uninstallAppAndReboot: %s" % data)
return
@ -897,7 +907,7 @@ class DeviceManagerSUT(DeviceManager):
self._waitForRebootPing(serverSocket)
def getCurrentTime(self):
return int(self._runCmds([{ 'cmd': 'clok' }]).strip())
return int(self._runCmds([{'cmd': 'clok'}]).strip())
def _formatEnvString(self, env):
"""
@ -907,7 +917,7 @@ class DeviceManagerSUT(DeviceManager):
Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
If env is None or '' return '' (empty quoted string)
"""
if (env == None or env == ''):
if (env is None or env == ''):
return ''
retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
@ -922,7 +932,8 @@ class DeviceManagerSUT(DeviceManager):
NOTE: this only works on a tegra ATM
supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080
supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900,
1680x1050, 1920x1080
"""
if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng':
self._logger.warning("unable to adjust screen resolution on non Tegra device")
@ -930,9 +941,11 @@ class DeviceManagerSUT(DeviceManager):
results = self.getInfo('screen')
parts = results['screen'][0].split(':')
self._logger.debug("we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0]))
self._logger.debug("we have a current resolution of %s, %s" %
(parts[1].split()[0], parts[2].split()[0]))
#verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
# verify screen type is valid, and set it to the proper value
# (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
screentype = -1
if (type == 'hdmi'):
screentype = 5
@ -941,7 +954,7 @@ class DeviceManagerSUT(DeviceManager):
else:
return False
#verify we have numbers
# verify we have numbers
if not (isinstance(width, int) and isinstance(height, int)):
return False
@ -953,8 +966,10 @@ class DeviceManagerSUT(DeviceManager):
self._logger.debug("adjusting screen resolution to %s, %s and rebooting" % (width, height))
self._runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }])
self._runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }])
self._runCmds(
[{'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width)}])
self._runCmds(
[{'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height)}])
def chmodDir(self, remoteDir, **kwargs):
self._runCmds([{ 'cmd': "chmod "+remoteDir }])
self._runCmds([{'cmd': "chmod " + remoteDir}])

View File

@ -16,121 +16,128 @@ import mozdevice
import mozlog
import argparse
class DMCli(object):
def __init__(self):
self.commands = { 'deviceroot': { 'function': self.deviceroot,
'help': 'get device root directory for storing temporary files' },
'install': { 'function': self.install,
'args': [ { 'name': 'file' } ],
'help': 'push this package file to the device and install it' },
'uninstall': { 'function': self.uninstall,
'args': [ { 'name': 'packagename' } ],
'help': 'uninstall the named app from the device' },
'killapp': { 'function': self.kill,
'args': [ { 'name': 'process_name', 'nargs': '*' } ],
'help': 'kills any processes with name(s) on device' },
'launchapp': { 'function': self.launchapp,
'args': [ { 'name': 'appname' },
{ 'name': 'activity_name' },
{ 'name': '--intent',
'action': 'store',
'default': 'android.intent.action.VIEW' },
{ 'name': '--url',
'action': 'store' },
{ 'name': '--no-fail-if-running',
'action': 'store_true',
'help': 'Don\'t fail if application is already running' }
self.commands = {'deviceroot': {'function': self.deviceroot,
'help': 'get device root directory for storing temporary '
'files'},
'install': {'function': self.install,
'args': [{'name': 'file'}],
'help': 'push this package file to the device'
' and install it'},
'uninstall': {'function': self.uninstall,
'args': [{'name': 'packagename'}],
'help': 'uninstall the named app from the device'},
'killapp': {'function': self.kill,
'args': [{'name': 'process_name', 'nargs': '*'}],
'help': 'kills any processes with name(s) on device'},
'launchapp': {'function': self.launchapp,
'args': [{'name': 'appname'},
{'name': 'activity_name'},
{'name': '--intent',
'action': 'store',
'default': 'android.intent.action.VIEW'},
{'name': '--url',
'action': 'store'},
{'name': '--no-fail-if-running',
'action': 'store_true',
'help': 'Don\'t fail if application is'
' already running'}
],
'help': 'launches application on device' },
'listapps': { 'function': self.listapps,
'help': 'list applications on device' },
'push': { 'function': self.push,
'args': [ { 'name': 'local_file' },
{ 'name': 'remote_file' }
],
'help': 'copy file/dir to device' },
'pull': { 'function': self.pull,
'args': [ { 'name': 'local_file' },
{ 'name': 'remote_file', 'nargs': '?' } ],
'help': 'copy file/dir from device' },
'shell': { 'function': self.shell,
'args': [ { 'name': 'command', 'nargs': argparse.REMAINDER },
{ 'name': '--root', 'action': 'store_true',
'help': 'Run command as root' }],
'help': 'run shell command on device' },
'info': { 'function': self.getinfo,
'args': [ { 'name': 'directive', 'nargs': '?' } ],
'help': 'get information on specified '
'aspect of the device (if no argument '
'given, print all available information)'
'help': 'launches application on device'},
'listapps': {'function': self.listapps,
'help': 'list applications on device'},
'push': {'function': self.push,
'args': [{'name': 'local_file'},
{'name': 'remote_file'}
],
'help': 'copy file/dir to device'},
'pull': {'function': self.pull,
'args': [{'name': 'local_file'},
{'name': 'remote_file', 'nargs': '?'}],
'help': 'copy file/dir from device'},
'shell': {'function': self.shell,
'args': [{'name': 'command', 'nargs': argparse.REMAINDER},
{'name': '--root', 'action': 'store_true',
'help': 'Run command as root'}],
'help': 'run shell command on device'},
'info': {'function': self.getinfo,
'args': [{'name': 'directive', 'nargs': '?'}],
'help': 'get information on specified '
'aspect of the device (if no argument '
'given, print all available information)'
},
'ps': {'function': self.processlist,
'help': 'get information on running processes on device'
},
'logcat': {'function': self.logcat,
'help': 'get logcat from device'
},
'ps': { 'function': self.processlist,
'help': 'get information on running processes on device'
'ls': {'function': self.listfiles,
'args': [{'name': 'remote_dir'}],
'help': 'list files on device'
},
'logcat' : { 'function': self.logcat,
'help': 'get logcat from device'
'rm': {'function': self.removefile,
'args': [{'name': 'remote_file'}],
'help': 'remove file from device'
},
'ls': { 'function': self.listfiles,
'args': [ { 'name': 'remote_dir' } ],
'help': 'list files on device'
},
'rm': { 'function': self.removefile,
'args': [ { 'name': 'remote_file' } ],
'help': 'remove file from device'
},
'isdir': { 'function': self.isdir,
'args': [ { 'name': 'remote_dir' } ],
'help': 'print if remote file is a directory'
},
'mkdir': { 'function': self.mkdir,
'args': [ { 'name': 'remote_dir' } ],
'help': 'makes a directory on device'
},
'rmdir': { 'function': self.rmdir,
'args': [ { 'name': 'remote_dir' } ],
'help': 'recursively remove directory from device'
},
'screencap': { 'function': self.screencap,
'args': [ { 'name': 'png_file' } ],
'help': 'capture screenshot of device in action'
},
'sutver': { 'function': self.sutver,
'help': 'SUTAgent\'s product name and version (SUT only)'
'isdir': {'function': self.isdir,
'args': [{'name': 'remote_dir'}],
'help': 'print if remote file is a directory'
},
'clearlogcat': { 'function': self.clearlogcat,
'help': 'clear the logcat'
'mkdir': {'function': self.mkdir,
'args': [{'name': 'remote_dir'}],
'help': 'makes a directory on device'
},
'rmdir': {'function': self.rmdir,
'args': [{'name': 'remote_dir'}],
'help': 'recursively remove directory from device'
},
'screencap': {'function': self.screencap,
'args': [{'name': 'png_file'}],
'help': 'capture screenshot of device in action'
},
'sutver': {'function': self.sutver,
'help': 'SUTAgent\'s product name and version (SUT only)'
},
'clearlogcat': {'function': self.clearlogcat,
'help': 'clear the logcat'
},
'reboot': { 'function': self.reboot,
'help': 'reboot the device',
'args': [ { 'name': '--wait',
'action': 'store_true',
'help': 'Wait for device to come back up before exiting' } ]
'reboot': {'function': self.reboot,
'help': 'reboot the device',
'args': [{'name': '--wait',
'action': 'store_true',
'help': 'Wait for device to come back up'
' before exiting'}]
},
'isfile': { 'function': self.isfile,
'args': [ { 'name': 'remote_file' } ],
'help': 'check whether a file exists on the device'
},
'launchfennec': { 'function': self.launchfennec,
'args': [ { 'name': 'appname' },
{ 'name': '--intent', 'action': 'store',
'default': 'android.intent.action.VIEW' },
{ 'name': '--url', 'action': 'store' },
{ 'name': '--extra-args', 'action': 'store' },
{ 'name': '--mozenv', 'action': 'store',
'help': 'Gecko environment variables to set in "KEY1=VAL1 KEY2=VAL2" format' },
{ 'name': '--no-fail-if-running',
'action': 'store_true',
'help': 'Don\'t fail if application is already running' }
],
'help': 'launch fennec'
},
'getip': { 'function': self.getip,
'args': [ { 'name': 'interface', 'nargs': '*' } ],
'help': 'get the ip address of the device'
},
'isfile': {'function': self.isfile,
'args': [{'name': 'remote_file'}],
'help': 'check whether a file exists on the device'
},
'launchfennec': {'function': self.launchfennec,
'args': [{'name': 'appname'},
{'name': '--intent', 'action': 'store',
'default': 'android.intent.action.VIEW'},
{'name': '--url', 'action': 'store'},
{'name': '--extra-args', 'action': 'store'},
{'name': '--mozenv', 'action': 'store',
'help': 'Gecko environment variables to set'
' in "KEY1=VAL1 KEY2=VAL2" format'},
{'name': '--no-fail-if-running',
'action': 'store_true',
'help': 'Don\'t fail if application is '
'already running'}
],
'help': 'launch fennec'
},
'getip': {'function': self.getip,
'args': [{'name': 'interface', 'nargs': '*'}],
'help': 'get the ip address of the device'
}
}
}
self.parser = argparse.ArgumentParser()
self.add_options(self.parser)
@ -162,18 +169,18 @@ class DMCli(object):
help="Verbose output from DeviceManager",
default=bool(os.environ.get('VERBOSE')))
parser.add_argument("--host", action="store",
help="Device hostname (only if using TCP/IP, " \
"defaults to TEST_DEVICE environment " \
"variable if present)",
help="Device hostname (only if using TCP/IP, "
"defaults to TEST_DEVICE environment "
"variable if present)",
default=os.environ.get('TEST_DEVICE'))
parser.add_argument("-p", "--port", action="store",
type=int,
help="Custom device port (if using SUTAgent or "
"adb-over-tcp)", default=None)
parser.add_argument("-m", "--dmtype", action="store",
help="DeviceManager type (adb or sut, defaults " \
"to DM_TRANS environment variable, if " \
"present, or adb)",
help="DeviceManager type (adb or sut, defaults "
"to DM_TRANS environment variable, if "
"present, or adb)",
default=os.environ.get('DM_TRANS', 'adb'))
parser.add_argument("-d", "--hwid", action="store",
help="HWID", default=None)
@ -365,6 +372,7 @@ class DMCli(object):
else:
print(self.dm.getIP())
def cli(args=sys.argv[1:]):
# process the command line
cli = DMCli()

View File

@ -16,6 +16,7 @@ from devicemanagerADB import DeviceManagerADB
from devicemanagerSUT import DeviceManagerSUT
from devicemanager import DMError
class DroidMixin(object):
"""Mixin to extend DeviceManager with Android-specific functionality"""
@ -46,7 +47,7 @@ class DroidMixin(object):
raise DMError("Only one instance of an application may be running "
"at once")
acmd = [ "am", "start" ] + self._getExtraAmStartArgs() + \
acmd = ["am", "start"] + self._getExtraAmStartArgs() + \
["-W" if wait else '', "-n", "%s/%s" % (appName, activityName)]
if intent:
@ -103,7 +104,8 @@ class DroidMixin(object):
if extraArgs:
extras['args'] = " ".join(extraArgs)
self.launchApplication(appName, "org.mozilla.gecko.BrowserApp", intent, url=url, extras=extras,
self.launchApplication(appName, "org.mozilla.gecko.BrowserApp", intent, url=url,
extras=extras,
wait=wait, failIfRunning=failIfRunning)
def getInstalledApps(self):
@ -134,7 +136,8 @@ class DroidMixin(object):
"""
version = self.shellCheckOutput(["getprop", "ro.build.version.sdk"])
if int(version) >= version_codes.HONEYCOMB:
self.shellCheckOutput([ "am", "force-stop", appName ], root=self._stopApplicationNeedsRoot)
self.shellCheckOutput(["am", "force-stop", appName],
root=self._stopApplicationNeedsRoot)
else:
num_tries = 0
max_tries = 5
@ -151,6 +154,7 @@ class DroidMixin(object):
# racey, but it's the best we can do)
time.sleep(1)
class DroidADB(DeviceManagerADB, DroidMixin):
_stopApplicationNeedsRoot = False
@ -159,16 +163,17 @@ class DroidADB(DeviceManagerADB, DroidMixin):
package = None
data = None
try:
data = self.shellCheckOutput(["dumpsys", "window", "windows"], timeout=self.short_timeout)
data = self.shellCheckOutput(
["dumpsys", "window", "windows"], timeout=self.short_timeout)
except:
# dumpsys seems to intermittently fail (seen on 4.3 emulator), producing
# no output.
return ""
# "dumpsys window windows" produces many lines of input. The top/foreground
# activity is indicated by something like:
# mFocusedApp=AppWindowToken{483e6db0 token=HistoryRecord{484dcad8 com.mozilla.SUTAgentAndroid/.SUTAgentAndroid}}
# mFocusedApp=AppWindowToken{483e6db0 token=HistoryRecord{484dcad8 com.mozilla.SUTAgentAndroid/.SUTAgentAndroid}} # noqa
# or, on other devices:
# FocusedApplication: name='AppWindowToken{41a65340 token=ActivityRecord{418fbd68 org.mozilla.fennec_mozdev/org.mozilla.gecko.BrowserApp}}', dispatchingTimeout=5000.000ms
# FocusedApplication: name='AppWindowToken{41a65340 token=ActivityRecord{418fbd68 org.mozilla.fennec_mozdev/org.mozilla.gecko.BrowserApp}}', dispatchingTimeout=5000.000ms # noqa
# Extract this line, ending in the forward slash:
m = re.search('mFocusedApp(.+)/', data)
if not m:
@ -194,6 +199,7 @@ class DroidADB(DeviceManagerADB, DroidMixin):
# relying on convention
return '/data/data/%s' % packageName
class DroidSUT(DeviceManagerSUT, DroidMixin):
def _getExtraAmStartArgs(self):
@ -205,29 +211,32 @@ class DroidSUT(DeviceManagerSUT, DroidMixin):
infoDict = self.getInfo(directive="sutuserinfo")
if infoDict.get('sutuserinfo') and \
len(infoDict['sutuserinfo']) > 0:
userSerialString = infoDict['sutuserinfo'][0]
# user serial always an integer, see: http://developer.android.com/reference/android/os/UserManager.html#getSerialNumberForUser%28android.os.UserHandle%29
m = re.match('User Serial:([0-9]+)', userSerialString)
if m:
self._userSerial = m.group(1)
else:
self._userSerial = None
userSerialString = infoDict['sutuserinfo'][0]
# user serial always an integer, see:
# http://developer.android.com/reference/android/os/UserManager.html#getSerialNumberForUser%28android.os.UserHandle%29
m = re.match('User Serial:([0-9]+)', userSerialString)
if m:
self._userSerial = m.group(1)
else:
self._userSerial = None
else:
self._userSerial = None
if self._userSerial is not None:
return [ "--user", self._userSerial ]
return ["--user", self._userSerial]
return []
def getTopActivity(self):
return self._runCmds([{ 'cmd': "activity" }]).strip()
return self._runCmds([{'cmd': "activity"}]).strip()
def getAppRoot(self, packageName):
return self._runCmds([{ 'cmd': 'getapproot %s' % packageName }]).strip()
return self._runCmds([{'cmd': 'getapproot %s' % packageName}]).strip()
def DroidConnectByHWID(hwid, timeout=30, **kwargs):
"""Try to connect to the given device by waiting for it to show up using mDNS with the given timeout."""
"""Try to connect to the given device by waiting for it to show up using
mDNS with the given timeout."""
zc = Zeroconf(moznetwork.get_ip())
evt = threading.Event()

View File

@ -23,6 +23,7 @@ SCHEMA = {'Registration Server': (('IPAddr', ''),
('ENCR', ''),
('EAP', ''))}
def get_cfg(d, ini_path):
cfg = ConfigParser.RawConfigParser()
try:

View File

@ -17,9 +17,9 @@ BASE = 1
# February 2009: First Android update, officially called 1.1
BASE_1_1 = 2
# May 2009: Android 1.5
CUPCAKE = 3
CUPCAKE = 3
# September 2009: Android 1.6
DONUT = 4
DONUT = 4
# November 2009: Android 2.0
ECLAIR = 5
# December 2009: Android 2.0.1

View File

@ -11,13 +11,13 @@ deps = ['mozfile >= 1.0',
'mozlog >= 3.0',
'moznetwork >= 0.24',
'mozprocess >= 0.19',
]
]
setup(name=PACKAGE_NAME,
version=PACKAGE_VERSION,
description="Mozilla-authored device management",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='',
author='Mozilla Automation and Testing Team',
author_email='tools@lists.mozilla.org',

View File

@ -14,6 +14,7 @@ port = 0
heartbeat_port = 0
log_level = logging.ERROR
class DeviceManagerTestCase(unittest.TestCase):
"""DeviceManager tests should subclass this.
"""
@ -48,7 +49,7 @@ class DeviceManagerTestLoader(unittest.TestLoader):
for name in dir(module):
obj = getattr(module, name)
if (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, unittest.TestCase)) and \
(not self.isTestDevice or obj.runs_on_test_device):
issubclass(obj, unittest.TestCase)) and \
(not self.isTestDevice or obj.runs_on_test_device):
tests.append(self.loadTestsFromTestCase(obj))
return self.suiteClass(tests)

View File

@ -46,7 +46,7 @@ def main(ip, port, heartbeat_port, scripts, directory, isTestDevice, verbose):
genfiles.clean_test_files()
if __name__ == "__main__":
if __name__ == "__main__":
default_ip = '127.0.0.1'
default_port = 20701

View File

@ -8,6 +8,7 @@ from time import strptime
from dmunit import DeviceManagerTestCase, heartbeat_port
class DataChannelTestCase(DeviceManagerTestCase):
runs_on_test_device = False
@ -36,12 +37,12 @@ class DataChannelTestCase(DeviceManagerTestCase):
if not capturedHeader:
m = re.match(r"(.*?) trace output", data)
self.assertNotEqual(m, None,
'trace output line does not match. The line: ' + str(data))
'trace output line does not match. The line: ' + str(data))
capturedHeader = True
# Check for standard heartbeat messsage
m = re.match(r"(.*?) Thump thump - (.*)", data)
if m == None:
if m is None:
# This isn't an error, it usually means we've obtained some
# unexpected data from the device
continue

View File

@ -7,6 +7,7 @@ from StringIO import StringIO
from dmunit import DeviceManagerTestCase
class ExecTestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -8,6 +8,7 @@ from StringIO import StringIO
from dmunit import DeviceManagerTestCase
class ExecEnvTestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -7,6 +7,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
class FileExistsTestCase(DeviceManagerTestCase):
"""This tests the "fileExists" command.
"""
@ -34,4 +35,3 @@ class FileExistsTestCase(DeviceManagerTestCase):
self.assertTrue(self.dm.fileExists(remote_path))
self.dm.removeFile(remote_path_file)
self.dm.removeDir(remote_path)

View File

@ -10,6 +10,7 @@ import tempfile
from mozdevice.devicemanager import DMError
from dmunit import DeviceManagerTestCase
class GetDirectoryTestCase(DeviceManagerTestCase):
def _setUp(self):
@ -46,5 +47,5 @@ class GetDirectoryTestCase(DeviceManagerTestCase):
self.assertTrue(os.path.exists(
os.path.join(self.localdestdir, 'push1', 'emptysub')))
self.assertRaises(DMError, self.dm.getDirectory,
'/dummy', os.path.join(self.localdestdir, '/none'))
'/dummy', os.path.join(self.localdestdir, '/none'))
self.assertFalse(os.path.exists(self.localdestdir + '/none'))

View File

@ -4,6 +4,7 @@
from dmunit import DeviceManagerTestCase
class InfoTestCase(DeviceManagerTestCase):
runs_on_test_device = False

View File

@ -7,6 +7,7 @@ import socket
from dmunit import DeviceManagerTestCase
class PromptTestCase(DeviceManagerTestCase):
def tearDown(self):

View File

@ -4,6 +4,7 @@
from dmunit import DeviceManagerTestCase
class ProcessListTestCase(DeviceManagerTestCase):
def runTest(self):
@ -24,4 +25,3 @@ class ProcessListTestCase(DeviceManagerTestCase):
self.assertGreater(len(item[1]), 0)
if len(item) > 2:
self.assertIsInstance(item[2], int)

View File

@ -9,6 +9,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
from mozdevice.devicemanager import DMError
class PullTestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -7,6 +7,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
class Push1TestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -7,6 +7,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
class Push2TestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -7,6 +7,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
class PushBinaryTestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -7,6 +7,7 @@ import posixpath
from dmunit import DeviceManagerTestCase
class PushSmallTextTestCase(DeviceManagerTestCase):
def runTest(self):

View File

@ -3,30 +3,31 @@ import mozdevice
import logging
import unittest
class LaunchTest(unittest.TestCase):
def test_nouserserial(self):
a = MockAgent(self, commands = [("ps",
"10029 549 com.android.launcher\n"
"10066 1198 com.twitter.android"),
("info sutuserinfo", ""),
("exec am start -W -n "
"org.mozilla.fennec/org.mozilla.gecko.BrowserApp -a "
"android.intent.action.VIEW",
"OK\nreturn code [0]")])
a = MockAgent(self, commands=[("ps",
"10029 549 com.android.launcher\n"
"10066 1198 com.twitter.android"),
("info sutuserinfo", ""),
("exec am start -W -n "
"org.mozilla.fennec/org.mozilla.gecko.BrowserApp -a "
"android.intent.action.VIEW",
"OK\nreturn code [0]")])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=logging.DEBUG)
d.launchFennec("org.mozilla.fennec")
a.wait()
def test_userserial(self):
a = MockAgent(self, commands = [("ps",
"10029 549 com.android.launcher\n"
"10066 1198 com.twitter.android"),
("info sutuserinfo", "User Serial:0"),
("exec am start --user 0 -W -n "
"org.mozilla.fennec/org.mozilla.gecko.BrowserApp -a "
"android.intent.action.VIEW",
"OK\nreturn code [0]")])
a = MockAgent(self, commands=[("ps",
"10029 549 com.android.launcher\n"
"10066 1198 com.twitter.android"),
("info sutuserinfo", "User Serial:0"),
("exec am start --user 0 -W -n "
"org.mozilla.fennec/org.mozilla.gecko.BrowserApp -a "
"android.intent.action.VIEW",
"OK\nreturn code [0]")])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=logging.DEBUG)
d.launchFennec("org.mozilla.fennec")
a.wait()

View File

@ -9,12 +9,13 @@ import time
from threading import Thread
class MockAgent(object):
MAX_WAIT_TIME_SECONDS = 10
SOCKET_TIMEOUT_SECONDS = 5
def __init__(self, tester, start_commands = None, commands = []):
def __init__(self, tester, start_commands=None, commands=[]):
if start_commands:
self.commands = start_commands
else:
@ -60,11 +61,11 @@ class MockAgent(object):
# send response and prompt separately to test for bug 789496
# FIXME: Improve the mock agent, since overloading the meaning
# of 'response' is getting confusing.
if response is None: # code for "shut down"
if response is None: # code for "shut down"
conn.shutdown(socket.SHUT_RDWR)
conn.close()
conn = None
elif type(response) is int: # code for "time out"
elif type(response) is int: # code for "time out"
max_timeout = 15.0
timeout = 0.0
interval = 0.1

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest

View File

@ -3,6 +3,7 @@ import mozdevice
import logging
import unittest
class BasicTest(unittest.TestCase):
def test_init(self):
@ -24,22 +25,22 @@ class BasicTest(unittest.TestCase):
def test_timeout_normal(self):
"""Tests DeviceManager timeout, normal case."""
a = MockAgent(self, commands = [("isdir /mnt/sdcard/tests", "TRUE"),
("cd /mnt/sdcard/tests", ""),
("ls", "test.txt"),
("rm /mnt/sdcard/tests/test.txt",
"Removed the file")])
a = MockAgent(self, commands=[("isdir /mnt/sdcard/tests", "TRUE"),
("cd /mnt/sdcard/tests", ""),
("ls", "test.txt"),
("rm /mnt/sdcard/tests/test.txt",
"Removed the file")])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=logging.DEBUG)
ret = d.removeFile('/mnt/sdcard/tests/test.txt')
self.assertEqual(ret, None) # if we didn't throw an exception, we're ok
self.assertEqual(ret, None) # if we didn't throw an exception, we're ok
a.wait()
def test_timeout_timeout(self):
"""Tests DeviceManager timeout, timeout case."""
a = MockAgent(self, commands = [("isdir /mnt/sdcard/tests", "TRUE"),
("cd /mnt/sdcard/tests", ""),
("ls", "test.txt"),
("rm /mnt/sdcard/tests/test.txt", 0)])
a = MockAgent(self, commands=[("isdir /mnt/sdcard/tests", "TRUE"),
("cd /mnt/sdcard/tests", ""),
("ls", "test.txt"),
("rm /mnt/sdcard/tests/test.txt", 0)])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=logging.DEBUG)
d.default_timeout = 1
exceptionThrown = False
@ -53,8 +54,8 @@ class BasicTest(unittest.TestCase):
def test_shell(self):
"""Tests shell command"""
for cmd in [ ("exec foobar", False), ("execsu foobar", True) ]:
for retcode in [ 1, 2 ]:
for cmd in [("exec foobar", False), ("execsu foobar", True)]:
for retcode in [1, 2]:
a = MockAgent(self, commands=[(cmd[0],
"\nreturn code [%s]" % retcode)])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port)

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest
@ -9,9 +9,10 @@ class TestChmod(unittest.TestCase):
def test_chmod(self):
command = [('chmod /mnt/sdcard/test', 'Changing permissions for /storage/emulated/legacy/Test\n'
' <empty>\n'
'chmod /storage/emulated/legacy/Test ok\n')]
command = [('chmod /mnt/sdcard/test',
'Changing permissions for /storage/emulated/legacy/Test\n'
' <empty>\n'
'chmod /storage/emulated/legacy/Test ok\n')]
m = MockAgent(self, commands=command)
d = mozdevice.DroidSUT('127.0.0.1', port=m.port, logLevel=logging.DEBUG)

View File

@ -10,7 +10,9 @@ import logging
import unittest
from sut import MockAgent
class CopyTreeTest(unittest.TestCase):
def test_copyFile(self):
commands = [('dd if=/mnt/sdcard/tests/test.txt of=/mnt/sdcard/tests/test2.txt', ''),
('isdir /mnt/sdcard/tests', 'TRUE'),
@ -21,7 +23,7 @@ class CopyTreeTest(unittest.TestCase):
d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=logging.DEBUG)
self.assertEqual(None, d.copyTree('/mnt/sdcard/tests/test.txt',
'/mnt/sdcard/tests/test2.txt'))
'/mnt/sdcard/tests/test2.txt'))
expected = (commands[3][1].strip()).split('\n')
self.assertEqual(expected, d.listFiles('/mnt/sdcard/tests'))
@ -33,10 +35,10 @@ class CopyTreeTest(unittest.TestCase):
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port,
logLevel=logging.DEBUG)
logLevel=logging.DEBUG)
self.assertEqual(None, d.copyTree('/mnt/sdcard/tests/foo',
'/mnt/sdcard/tests/bar'))
'/mnt/sdcard/tests/bar'))
expected = (commands[3][1].strip()).split('\n')
self.assertEqual(expected, d.listFiles('/mnt/sdcard/tests'))
@ -52,11 +54,11 @@ class CopyTreeTest(unittest.TestCase):
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port,
logLevel=logging.DEBUG)
logLevel=logging.DEBUG)
self.assertTrue(d.dirExists('/mnt/sdcard/tests/foo/bar'))
self.assertEqual(None, d.copyTree('/mnt/sdcard/tests/foo',
'/mnt/sdcard/tests/foo2'))
'/mnt/sdcard/tests/foo2'))
expected = (commands[4][1].strip()).split('\n')
self.assertEqual(expected, d.listFiles('/mnt/sdcard/tests'))
self.assertTrue(d.fileExists('/mnt/sdcard/tests/foo2/bar'))

View File

@ -2,6 +2,7 @@ from sut import MockAgent
import mozdevice
import unittest
class FileExistsTest(unittest.TestCase):
commands = [('isdir /', 'TRUE'),
@ -26,4 +27,3 @@ class FileExistsTest(unittest.TestCase):
if __name__ == '__main__':
unittest.main()

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import re

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest

View File

@ -11,27 +11,28 @@ class TestLogCat(unittest.TestCase):
def test_getLogcat(self):
logcat_output = ("07-17 00:51:10.377 I/SUTAgentAndroid( 2933): onCreate\r\n"
"07-17 00:51:10.457 D/dalvikvm( 2933): GC_CONCURRENT freed 351K, 17% free 2523K/3008K, paused 5ms+2ms, total 38ms\r\n"
"07-17 00:51:10.497 I/SUTAgentAndroid( 2933): Caught exception creating file in /data/local/tmp: open failed: EACCES (Permission denied)\r\n"
"07-17 00:51:10.507 E/SUTAgentAndroid( 2933): ERROR: Cannot access world writeable test root\r\n"
"07-17 00:51:10.547 D/GeckoHealthRec( 3253): Initializing profile cache.\r\n"
"07-17 00:51:10.607 D/GeckoHealthRec( 3253): Looking for /data/data/org.mozilla.fennec/files/mozilla/c09kfhne.default/times.json\r\n"
"07-17 00:51:10.637 D/GeckoHealthRec( 3253): Using times.json for profile creation time.\r\n"
"07-17 00:51:10.707 D/GeckoHealthRec( 3253): Incorporating environment: times.json profile creation = 1374026758604\r\n"
"07-17 00:51:10.507 D/GeckoHealthRec( 3253): Requested prefs.\r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): \r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Private Dirty Memory 3176 kb\r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Proportional Set Size Memory 5679 kb\r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Shared Dirty Memory 9216 kb\r\n"
"07-17 06:55:21.627 I/SUTAgentAndroid( 3876): 127.0.0.1 : execsu /system/bin/logcat -v time -d dalvikvm:I "
"ConnectivityService:S WifiMonitor:S WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S\r\n"
"07-17 06:55:21.827 I/dalvikvm-heap( 3876): Grow heap (frag case) to 3.019MB for 102496-byte allocation\r\n"
"return code [0]")
logcat_output = (
"07-17 00:51:10.377 I/SUTAgentAndroid( 2933): onCreate\r\n"
"07-17 00:51:10.457 D/dalvikvm( 2933): GC_CONCURRENT freed 351K, 17% free 2523K/3008K, paused 5ms+2ms, total 38ms\r\n" # noqa
"07-17 00:51:10.497 I/SUTAgentAndroid( 2933): Caught exception creating file in /data/local/tmp: open failed: EACCES (Permission denied)\r\n" # noqa
"07-17 00:51:10.507 E/SUTAgentAndroid( 2933): ERROR: Cannot access world writeable test root\r\n" # noqa
"07-17 00:51:10.547 D/GeckoHealthRec( 3253): Initializing profile cache.\r\n"
"07-17 00:51:10.607 D/GeckoHealthRec( 3253): Looking for /data/data/org.mozilla.fennec/files/mozilla/c09kfhne.default/times.json\r\n" # noqa
"07-17 00:51:10.637 D/GeckoHealthRec( 3253): Using times.json for profile creation time.\r\n" # noqa
"07-17 00:51:10.707 D/GeckoHealthRec( 3253): Incorporating environment: times.json profile creation = 1374026758604\r\n" # noqa
"07-17 00:51:10.507 D/GeckoHealthRec( 3253): Requested prefs.\r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): \r\n"
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Private Dirty Memory 3176 kb\r\n" # noqa
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Proportional Set Size Memory 5679 kb\r\n" # noqa
"07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Shared Dirty Memory 9216 kb\r\n" # noqa
"07-17 06:55:21.627 I/SUTAgentAndroid( 3876): 127.0.0.1 : execsu /system/bin/logcat -v time -d dalvikvm:I " # noqa
"ConnectivityService:S WifiMonitor:S WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S\r\n" # noqa
"07-17 06:55:21.827 I/dalvikvm-heap( 3876): Grow heap (frag case) to 3.019MB for 102496-byte allocation\r\n" # noqa
"return code [0]")
inp = ("execsu /system/bin/logcat -v time -d "
"dalvikvm:I ConnectivityService:S WifiMonitor:S "
"WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S")
"dalvikvm:I ConnectivityService:S WifiMonitor:S "
"WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S")
commands = [(inp, logcat_output)]
m = MockAgent(self, commands=commands)

View File

@ -6,6 +6,7 @@ import logging
import unittest
from sut import MockAgent
class MkDirsTest(unittest.TestCase):
def test_mkdirs(self):
@ -26,9 +27,10 @@ class MkDirsTest(unittest.TestCase):
('isdir /mnt/sdcard', 'TRUE'),
('isdir /mnt/sdcard/baz', 'FALSE'),
('mkdr /mnt/sdcard/baz',
'##AGENT-WARNING## Could not create the directory /mnt/sdcard/baz')],
"##AGENT-WARNING## "
"Could not create the directory /mnt/sdcard/baz")],
'expectException': True},
]
]
for subTest in subTests:
a = MockAgent(self, commands=subTest['cmds'])

View File

@ -10,7 +10,9 @@ import logging
import unittest
from sut import MockAgent
class MoveTreeTest(unittest.TestCase):
def test_moveFile(self):
commands = [('mv /mnt/sdcard/tests/test.txt /mnt/sdcard/tests/test1.txt', ''),
('isdir /mnt/sdcard/tests', 'TRUE'),
@ -23,7 +25,7 @@ class MoveTreeTest(unittest.TestCase):
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=logging.DEBUG)
self.assertEqual(None, d.moveTree('/mnt/sdcard/tests/test.txt',
'/mnt/sdcard/tests/test1.txt'))
'/mnt/sdcard/tests/test1.txt'))
self.assertFalse(d.fileExists('/mnt/sdcard/tests/test.txt'))
self.assertTrue(d.fileExists('/mnt/sdcard/tests/test1.txt'))
@ -36,7 +38,7 @@ class MoveTreeTest(unittest.TestCase):
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=logging.DEBUG)
self.assertEqual(None, d.moveTree('/mnt/sdcard/tests/foo',
'/mnt/sdcard/tests/bar'))
'/mnt/sdcard/tests/bar'))
self.assertTrue(d.fileExists('/mnt/sdcard/tests/bar'))
def test_moveNonEmptyDir(self):
@ -51,11 +53,11 @@ class MoveTreeTest(unittest.TestCase):
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port,
logLevel=logging.DEBUG)
logLevel=logging.DEBUG)
self.assertTrue(d.dirExists('/mnt/sdcard/tests/foo/bar'))
self.assertEqual(None, d.moveTree('/mnt/sdcard/tests/foo',
'/mnt/sdcard/tests/foo2'))
'/mnt/sdcard/tests/foo2'))
self.assertTrue(d.fileExists('/mnt/sdcard/tests/foo2'))
self.assertTrue(d.fileExists('/mnt/sdcard/tests/foo2/bar'))

View File

@ -2,6 +2,7 @@ from sut import MockAgent
import mozdevice
import unittest
class PsTest(unittest.TestCase):
pscommands = [('ps',

View File

@ -3,21 +3,22 @@ import mozdevice
import logging
import unittest
class PullTest(unittest.TestCase):
def test_pull_success(self):
for count in [ 1, 4, 1024, 2048 ]:
for count in [1, 4, 1024, 2048]:
cheeseburgers = ""
for i in range(count):
cheeseburgers += "cheeseburgers"
# pull file is kind of gross, make sure we can still execute commands after it's done
remoteName = "/mnt/sdcard/cheeseburgers"
a = MockAgent(self, commands = [("pull %s" % remoteName,
"%s,%s\n%s" % (remoteName,
len(cheeseburgers),
cheeseburgers)),
("isdir /mnt/sdcard", "TRUE")])
a = MockAgent(self, commands=[("pull %s" % remoteName,
"%s,%s\n%s" % (remoteName,
len(cheeseburgers),
cheeseburgers)),
("isdir /mnt/sdcard", "TRUE")])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port,
logLevel=logging.DEBUG)
@ -30,9 +31,9 @@ class PullTest(unittest.TestCase):
# this test simulates only receiving a few bytes of what we expect
# to be larger file
remoteName = "/mnt/sdcard/cheeseburgers"
a = MockAgent(self, commands = [("pull %s" % remoteName,
"%s,15\n%s" % (remoteName,
"cheeseburgh"))])
a = MockAgent(self, commands=[("pull %s" % remoteName,
"%s,15\n%s" % (remoteName,
"cheeseburgh"))])
d = mozdevice.DroidSUT("127.0.0.1", port=a.port,
logLevel=logging.DEBUG)
exceptionThrown = False
@ -44,5 +45,3 @@ class PullTest(unittest.TestCase):
if __name__ == '__main__':
unittest.main()

View File

@ -7,6 +7,7 @@ import hashlib
import tempfile
import os
class PushTest(unittest.TestCase):
def test_push(self):
@ -16,10 +17,10 @@ class PushTest(unittest.TestCase):
expectedResponse = mdsum.hexdigest()
# (good response, no exception), (bad response, exception)
for response in [ (expectedResponse, False), ("BADHASH", True) ]:
for response in [(expectedResponse, False), ("BADHASH", True)]:
cmd = "push /mnt/sdcard/foobar %s\r\n%s" % (len(pushfile), pushfile)
a = MockAgent(self, commands = [("isdir /mnt/sdcard", "TRUE"),
(cmd, response[0])])
a = MockAgent(self, commands=[("isdir /mnt/sdcard", "TRUE"),
(cmd, response[0])])
exceptionThrown = False
with tempfile.NamedTemporaryFile() as f:
try:
@ -46,29 +47,29 @@ class PushTest(unittest.TestCase):
f.write(pushfile)
f.flush()
subTests = [ { 'cmds': [ ("isdir /mnt/sdcard/baz", "TRUE"),
("push /mnt/sdcard/baz/%s %s\r\n%s" %
(os.path.basename(f.name), len(pushfile),
pushfile),
expectedFileResponse) ],
'expectException': False },
{ 'cmds': [ ("isdir /mnt/sdcard/baz", "TRUE"),
("push /mnt/sdcard/baz/%s %s\r\n%s" %
(os.path.basename(f.name), len(pushfile),
pushfile),
"BADHASH") ],
'expectException': True },
{ 'cmds': [ ("isdir /mnt/sdcard/baz", "FALSE"),
('info os', 'android'),
("isdir /mnt", "FALSE"),
("mkdr /mnt",
"##AGENT-WARNING## Could not create the directory /mnt") ],
'expectException': True },
subTests = [{'cmds': [("isdir /mnt/sdcard/baz", "TRUE"),
("push /mnt/sdcard/baz/%s %s\r\n%s" %
(os.path.basename(f.name), len(pushfile),
pushfile),
expectedFileResponse)],
'expectException': False},
{'cmds': [("isdir /mnt/sdcard/baz", "TRUE"),
("push /mnt/sdcard/baz/%s %s\r\n%s" %
(os.path.basename(f.name), len(pushfile),
pushfile),
"BADHASH")],
'expectException': True},
{'cmds': [("isdir /mnt/sdcard/baz", "FALSE"),
('info os', 'android'),
("isdir /mnt", "FALSE"),
("mkdr /mnt",
"##AGENT-WARNING## Could not create the directory /mnt")],
'expectException': True},
]
]
for subTest in subTests:
a = MockAgent(self, commands = subTest['cmds'])
a = MockAgent(self, commands=subTest['cmds'])
exceptionThrown = False
try:

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest
@ -10,10 +10,10 @@ class TestRemove(unittest.TestCase):
def test_removeDir(self):
commands = [("isdir /mnt/sdcard/test", "TRUE"),
("rmdr /mnt/sdcard/test", "Deleting file(s) from "
"/storage/emulated/legacy/Moztest\n"
" <empty>\n"
"Deleting directory "
"/storage/emulated/legacy/Moztest\n")]
"/storage/emulated/legacy/Moztest\n"
" <empty>\n"
"Deleting directory "
"/storage/emulated/legacy/Moztest\n")]
m = MockAgent(self, commands=commands)
d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=logging.DEBUG)

View File

@ -1,4 +1,4 @@
#/usr/bin/env python
#!/usr/bin/env python
import mozdevice
import logging
import unittest

View File

@ -1,3 +1,4 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -27,7 +27,8 @@ __all__ = ['extract_tarball',
'NamedTemporaryFile',
'TemporaryDirectory']
### utilities for extracting archives
# utilities for extracting archives
def extract_tarball(src, dest):
"""extract a .tar file"""
@ -122,7 +123,7 @@ def extract(src, dest=None):
return top_level_files
### utilities for removal of files and directories
# utilities for removal of files and directories
def rmtree(dir):
"""Deprecated wrapper method to remove a directory tree.
@ -161,7 +162,7 @@ def _call_windows_retry(func, args=(), retry_max=5, retry_delay=0.5):
retry_count += 1
print '%s() failed for "%s". Reason: %s (%s). Retrying...' % \
(func.__name__, args, e.strerror, e.errno)
(func.__name__, args, e.strerror, e.errno)
time.sleep(retry_count * retry_delay)
else:
# If no exception has been thrown it should be done
@ -261,17 +262,18 @@ def depth(directory):
# ASCII delimeters
ascii_delimeters = {
'vertical_line' : '|',
'item_marker' : '+',
'last_child' : '\\'
}
'vertical_line': '|',
'item_marker': '+',
'last_child': '\\'
}
# unicode delimiters
unicode_delimeters = {
'vertical_line' : '',
'item_marker' : '',
'last_child' : ''
}
'vertical_line': '',
'item_marker': '',
'last_child': ''
}
def tree(directory,
item_marker=unicode_delimeters['item_marker'],
@ -319,21 +321,21 @@ def tree(directory,
# append the directory and piece of tree structure
# if the top-level entry directory, print as passed
retval.append('%s%s%s'% (''.join(indent[:-1]),
dirpath_mark,
basename if retval else directory))
retval.append('%s%s%s' % (''.join(indent[:-1]),
dirpath_mark,
basename if retval else directory))
# add the files
if filenames:
last_file = filenames[-1]
retval.extend([('%s%s%s' % (''.join(indent),
files_end if filename == last_file else item_marker,
filename))
for index, filename in enumerate(filenames)])
for index, filename in enumerate(filenames)])
return '\n'.join(retval)
### utilities for temporary resources
# utilities for temporary resources
class NamedTemporaryFile(object):
"""
@ -353,6 +355,7 @@ class NamedTemporaryFile(object):
see https://bugzilla.mozilla.org/show_bug.cgi?id=821362
"""
def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='tmp',
dir=None, delete=True):
@ -410,7 +413,7 @@ def TemporaryDirectory():
shutil.rmtree(tempdir)
### utilities dealing with URLs
# utilities dealing with URLs
def is_url(thing):
"""
@ -425,6 +428,7 @@ def is_url(thing):
else:
return len(parsed[0]) >= 2
def load(resource):
"""
open a file or URL for reading. If the passed resource string is not a URL,
@ -443,4 +447,3 @@ def load(resource):
return file(resource)
return urllib2.urlopen(resource)

View File

@ -11,7 +11,7 @@ setup(name=PACKAGE_NAME,
version=PACKAGE_VERSION,
description="Library of file utilities for use in Mozilla testing",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Tools team',
author_email='tools@lists.mozilla.org',

View File

@ -118,7 +118,7 @@ class TestExtract(unittest.TestCase):
os.rmdir(dest)
self.assertTrue(isinstance(exception, Exception))
### utility functions
# utility functions
def create_tarball(self):
"""create a stub tarball for testing"""

View File

@ -26,6 +26,7 @@ def mark_readonly(path):
class FileOpenCloseThread(threading.Thread):
"""Helper thread for asynchronous file handling"""
def __init__(self, path, delay, delete=False):
threading.Thread.__init__(self)
self.file_opened = threading.Event()
@ -201,6 +202,7 @@ class MozfileRemoveTestCase(unittest.TestCase):
class MozFileMoveTestCase(unittest.TestCase):
def setUp(self):
# Generate a stub
self.tempdir = stubs.create_stub()

View File

@ -44,3 +44,5 @@ content from the current directory, defines a single API endpoint
from mozhttpd import MozHttpd, Request, RequestHandler, main
from handlers import json_response
__all__ = ['MozHttpd', 'Request', 'RequestHandler', 'main', 'json_response']

View File

@ -4,12 +4,13 @@
import json
def json_response(func):
""" Translates results of 'func' into a JSON response. """
def wrap(*a, **kw):
(code, data) = func(*a, **kw)
json_data = json.dumps(data)
return (code, { 'Content-type': 'application/json',
'Content-Length': len(json_data) }, json_data)
return (code, {'Content-type': 'application/json',
'Content-Length': len(json_data)}, json_data)
return wrap

View File

@ -20,6 +20,7 @@ import moznetwork
import time
from SocketServer import ThreadingMixIn
class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
allow_reuse_address = True
acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
@ -62,7 +63,7 @@ class Request(object):
class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
docroot = os.getcwd() # current working directory at time of import
docroot = os.getcwd() # current working directory at time of import
proxy_host_dirs = False
request_log = []
log_requests = False
@ -74,9 +75,9 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def _try_handler(self, method):
if self.log_requests:
self.request_log.append({ 'method': method,
'path': self.request.path,
'time': time.time() })
self.request_log.append({'method': method,
'path': self.request.path,
'time': time.time()})
handlers = [handler for handler in self.urlhandlers
if handler['method'] == method]
@ -162,11 +163,11 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir): continue
if word in (os.curdir, os.pardir):
continue
path = os.path.join(path, word)
return path
# I found on my local network that calls to this were timing out
# I believe all of these calls are from log_message
def address_string(self):
@ -261,7 +262,7 @@ class MozHttpd(object):
self.httpd.serve_forever()
else:
self.server = threading.Thread(target=self.httpd.serve_forever)
self.server.setDaemon(True) # don't hang on exit
self.server.setDaemon(True) # don't hang on exit
self.server.start()
def stop(self):
@ -271,7 +272,7 @@ class MozHttpd(object):
If the server is not running, this method has no effect.
"""
if self.httpd:
### FIXME: There is no shutdown() method in Python 2.4...
# FIXME: There is no shutdown() method in Python 2.4...
try:
self.httpd.shutdown()
except AttributeError:

View File

@ -11,7 +11,7 @@ setup(name='mozhttpd',
version=PACKAGE_VERSION,
description="Python webserver intended for use with Mozilla testing",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Testing Team',
author_email='tools@lists.mozilla.org',
@ -27,4 +27,3 @@ setup(name='mozhttpd',
mozhttpd = mozhttpd:main
""",
)

View File

@ -14,6 +14,7 @@ import tempfile
here = os.path.dirname(os.path.abspath(__file__))
class ApiTest(unittest.TestCase):
resource_get_called = 0
resource_post_called = 0
@ -22,23 +23,23 @@ class ApiTest(unittest.TestCase):
@mozhttpd.handlers.json_response
def resource_get(self, request, objid):
self.resource_get_called += 1
return (200, { 'called': self.resource_get_called,
'id': objid,
'query': request.query })
return (200, {'called': self.resource_get_called,
'id': objid,
'query': request.query})
@mozhttpd.handlers.json_response
def resource_post(self, request):
self.resource_post_called += 1
return (201, { 'called': self.resource_post_called,
'data': json.loads(request.body),
'query': request.query })
return (201, {'called': self.resource_post_called,
'data': json.loads(request.body),
'query': request.query})
@mozhttpd.handlers.json_response
def resource_del(self, request, objid):
self.resource_del_called += 1
return (200, { 'called': self.resource_del_called,
'id': objid,
'query': request.query })
return (200, {'called': self.resource_del_called,
'id': objid,
'query': request.query})
def get_url(self, path, server_port, querystr):
url = "http://127.0.0.1:%s%s" % (server_port, path)
@ -54,13 +55,13 @@ class ApiTest(unittest.TestCase):
self.assertEqual(f.getcode(), 200)
except AttributeError:
pass # python 2.4
self.assertEqual(json.loads(f.read()), { 'called': 1, 'id': str(1), 'query': querystr })
self.assertEqual(json.loads(f.read()), {'called': 1, 'id': str(1), 'query': querystr})
self.assertEqual(self.resource_get_called, 1)
def try_post(self, server_port, querystr):
self.resource_post_called = 0
postdata = { 'hamburgers': '1234' }
postdata = {'hamburgers': '1234'}
try:
f = urllib2.urlopen(self.get_url('/api/resource/', server_port, querystr),
data=json.dumps(postdata))
@ -71,9 +72,9 @@ class ApiTest(unittest.TestCase):
else:
self.assertEqual(f.getcode(), 201)
body = f.read()
self.assertEqual(json.loads(body), { 'called': 1,
'data': postdata,
'query': querystr })
self.assertEqual(json.loads(body), {'called': 1,
'data': postdata,
'query': querystr})
self.assertEqual(self.resource_post_called, 1)
def try_del(self, server_port, querystr):
@ -88,21 +89,21 @@ class ApiTest(unittest.TestCase):
self.assertEqual(f.getcode(), 200)
except AttributeError:
pass # python 2.4
self.assertEqual(json.loads(f.read()), { 'called': 1, 'id': str(1), 'query': querystr })
self.assertEqual(json.loads(f.read()), {'called': 1, 'id': str(1), 'query': querystr})
self.assertEqual(self.resource_del_called, 1)
def test_api(self):
httpd = mozhttpd.MozHttpd(port=0,
urlhandlers = [ { 'method': 'GET',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_get },
{ 'method': 'POST',
'path': '/api/resource/?',
'function': self.resource_post },
{ 'method': 'DEL',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_del }
])
urlhandlers=[{'method': 'GET',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_get},
{'method': 'POST',
'path': '/api/resource/?',
'function': self.resource_post},
{'method': 'DEL',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_del}
])
httpd.start(block=False)
server_port = httpd.httpd.server_port
@ -169,9 +170,9 @@ class ApiTest(unittest.TestCase):
def test_api_with_docroot(self):
httpd = mozhttpd.MozHttpd(port=0, docroot=here,
urlhandlers = [ { 'method': 'GET',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_get } ])
urlhandlers=[{'method': 'GET',
'path': '/api/resource/([^/]+)/?',
'function': self.resource_get}])
httpd.start(block=False)
server_port = httpd.httpd.server_port
@ -187,6 +188,7 @@ class ApiTest(unittest.TestCase):
self.try_get(server_port, '')
self.try_get(server_port, '?foo=bar')
class ProxyTest(unittest.TestCase):
def tearDown(self):
@ -198,9 +200,11 @@ class ProxyTest(unittest.TestCase):
self.addCleanup(mozfile.remove, docroot)
hosts = ('mozilla.com', 'mozilla.org')
unproxied_host = 'notmozilla.org'
def url(host): return 'http://%s/' % host
index_filename = 'index.html'
def index_contents(host): return '%s index' % host
index = file(os.path.join(docroot, index_filename), 'w')

View File

@ -1,6 +1,7 @@
import mozhttpd
import unittest
class BaseUrlTest(unittest.TestCase):
def test_base_url(self):
@ -9,8 +10,8 @@ class BaseUrlTest(unittest.TestCase):
httpd.start(block=False)
self.assertEqual("http://127.0.0.1:%s/" % httpd.httpd.server_port,
httpd.get_url())
self.assertEqual("http://127.0.0.1:%s/cheezburgers.html" % \
httpd.httpd.server_port,
self.assertEqual("http://127.0.0.1:%s/cheezburgers.html" %
httpd.httpd.server_port,
httpd.get_url(path="/cheezburgers.html"))
httpd.stop()

View File

@ -12,6 +12,7 @@ import re
here = os.path.dirname(os.path.abspath(__file__))
class FileListingTest(unittest.TestCase):
def check_filelisting(self, path=''):
@ -21,14 +22,15 @@ class FileListingTest(unittest.TestCase):
httpd.start(block=False)
f = urllib2.urlopen("http://%s:%s/%s" % ('127.0.0.1', httpd.httpd.server_port, path))
for line in f.readlines():
webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@')
webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>',
'', line.strip('\n')).strip('/').strip().strip('@')
if webline and not webline.startswith("Directory listing for"):
self.assertTrue(webline in filelist,
"File %s in dir listing corresponds to a file" % webline)
filelist.remove(webline)
self.assertFalse(filelist, "Should have no items in filelist (%s) unaccounted for" % filelist)
self.assertFalse(
filelist, "Should have no items in filelist (%s) unaccounted for" % filelist)
def test_filelist(self):
self.check_filelisting()

View File

@ -9,7 +9,9 @@ import os
import unittest
import urllib2
class PathTest(unittest.TestCase):
def try_get(self, url, expected_contents):
f = urllib2.urlopen(url)
self.assertEqual(f.getcode(), 200)
@ -42,7 +44,7 @@ class PathTest(unittest.TestCase):
open(os.path.join(d2, "test2.txt"), "w").write("test 2 contents")
httpd = mozhttpd.MozHttpd(port=0,
path_mappings={'/abcxyz': d1,
'/abc': d2,}
'/abc': d2, }
)
httpd.start(block=False)
self.try_get(httpd.get_url("/abcxyz/test1.txt"), "test 1 contents")

View File

@ -9,6 +9,7 @@ import unittest
here = os.path.dirname(os.path.abspath(__file__))
class RequestLogTest(unittest.TestCase):
def check_logging(self, log_requests=False):

View File

@ -1,3 +1,4 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -20,23 +20,28 @@ from .string_version import StringVersion
# keep a copy of the os module since updating globals overrides this
_os = os
class unknown(object):
"""marker class for unknown information"""
def __nonzero__(self):
return False
def __str__(self):
return 'UNKNOWN'
unknown = unknown() # singleton
unknown = unknown() # singleton
def get_windows_version():
import ctypes
class OSVERSIONINFOEXW(ctypes.Structure):
_fields_ = [('dwOSVersionInfoSize', ctypes.c_ulong),
('dwMajorVersion', ctypes.c_ulong),
('dwMinorVersion', ctypes.c_ulong),
('dwBuildNumber', ctypes.c_ulong),
('dwPlatformId', ctypes.c_ulong),
('szCSDVersion', ctypes.c_wchar*128),
('szCSDVersion', ctypes.c_wchar * 128),
('wServicePackMajor', ctypes.c_ushort),
('wServicePackMinor', ctypes.c_ushort),
('wSuiteMask', ctypes.c_ushort),
@ -57,7 +62,7 @@ info = {'os': unknown,
'version': unknown,
'os_version': unknown,
'bits': unknown,
'has_sandbox': unknown }
'has_sandbox': unknown}
(system, node, release, version, machine, processor) = platform.uname()
(bits, linkage) = platform.architecture()
@ -138,7 +143,7 @@ elif processor == "Power Macintosh":
bits = re.search('(\d+)bit', bits).group(1)
info.update({'processor': processor,
'bits': int(bits),
})
})
if info['os'] == 'linux':
import ctypes
@ -161,7 +166,7 @@ def sanitize(info):
to handle universal Mac builds."""
if "processor" in info and info["processor"] == "universal-x86-x86_64":
# If we're running on OS X 10.6 or newer, assume 64-bit
if release[:4] >= "10.6": # Note this is a string comparison
if release[:4] >= "10.6": # Note this is a string comparison
info["processor"] = "x86_64"
info["bits"] = 64
else:
@ -169,6 +174,8 @@ def sanitize(info):
info["bits"] = 32
# method for updating information
def update(new_info):
"""
Update the info.
@ -193,9 +200,10 @@ def update(new_info):
for os_name in choices['os']:
globals()['is' + os_name.title()] = info['os'] == os_name
# unix is special
if isLinux or isBsd:
if isLinux or isBsd: # noqa
globals()['isUnix'] = True
def find_and_update_from_json(*dirs):
"""
Find a mozinfo.json file, load it, and update the info with the
@ -229,10 +237,11 @@ def find_and_update_from_json(*dirs):
return None
def output_to_file(path):
import json
with open(path, 'w') as f:
f.write(json.dumps(info));
f.write(json.dumps(info))
update({})
@ -248,7 +257,8 @@ __all__ += [
'find_and_update_from_json',
'output_to_file',
'StringVersion',
]
]
def main(args=None):
@ -279,7 +289,8 @@ def main(args=None):
print '%s choices: %s' % (key, ' '.join([str(choice)
for choice in choices[key]]))
flag = True
if flag: return
if flag:
return
# otherwise, print out all info
for key, value in info.items():

View File

@ -9,6 +9,7 @@ class StringVersion(str):
"""
A string version that can be compared with comparison operators.
"""
def __init__(self, vstring):
str.__init__(self, vstring)
self.version = LooseVersion(vstring)

View File

@ -13,7 +13,7 @@ setup(name='mozinfo',
version=PACKAGE_VERSION,
description="Library to get system information for use in Mozilla testing",
long_description="see http://mozbase.readthedocs.org",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Testing Team',
author_email='tools@lists.mozilla.org',

View File

@ -13,7 +13,9 @@ import tempfile
import unittest
import mozinfo
class TestMozinfo(unittest.TestCase):
def setUp(self):
reload(mozinfo)
self.tempdir = os.path.abspath(tempfile.mkdtemp())
@ -48,10 +50,10 @@ class TestMozinfo(unittest.TestCase):
def test_update_file_invalid_json(self):
"""Test that mozinfo.update handles invalid JSON correctly"""
j = os.path.join(self.tempdir,'test.json')
j = os.path.join(self.tempdir, 'test.json')
with open(j, 'w') as f:
f.write('invalid{"json":')
self.assertRaises(ValueError,mozinfo.update,[j])
self.assertRaises(ValueError, mozinfo.update, [j])
def test_find_and_update_file(self):
"""Test that mozinfo.find_and_update_from_json can
@ -70,7 +72,6 @@ class TestMozinfo(unittest.TestCase):
f.write('invalid{"json":')
self.assertRaises(ValueError, mozinfo.find_and_update_from_json, self.tempdir)
def test_find_and_update_file_mozbuild(self):
"""Test that mozinfo.find_and_update_from_json can
find mozinfo.json using the mozbuild module."""
@ -92,6 +93,7 @@ class TestMozinfo(unittest.TestCase):
class TestStringVersion(unittest.TestCase):
def test_os_version_is_a_StringVersion(self):
self.assertIsInstance(mozinfo.os_version, mozinfo.StringVersion)

View File

@ -1,3 +1,4 @@
# flake8: noqa
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -340,4 +340,3 @@ def uninstall_cli(argv=sys.argv[1:]):
# Run it
uninstall(argv[0])

View File

@ -15,7 +15,7 @@ PACKAGE_VERSION = '1.12'
deps = ['mozinfo >= 0.7',
'mozfile >= 1.0',
]
]
setup(name='mozInstall',
version=PACKAGE_VERSION,
@ -29,7 +29,7 @@ setup(name='mozInstall',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
],
],
keywords='mozilla',
author='Mozilla Automation and Tools team',
author_email='tools@lists.mozilla.org',
@ -39,7 +39,7 @@ setup(name='mozInstall',
include_package_data=True,
zip_safe=False,
install_requires=deps,
tests_require=['mozprocess >= 0.15',],
tests_require=['mozprocess >= 0.15', ],
# we have to generate two more executables for those systems that cannot run as Administrator
# and the filename containing "install" triggers the UAC
entry_points="""

View File

@ -14,6 +14,7 @@ import unittest
# Store file location at load time
here = os.path.dirname(os.path.abspath(__file__))
class TestMozInstall(unittest.TestCase):
@classmethod
@ -32,7 +33,8 @@ class TestMozInstall(unittest.TestCase):
def tearDown(self):
mozfile.rmtree(self.tempdir)
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe for mozinstall 1.12 and higher.")
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
"for mozinstall 1.12 and higher.")
def test_get_binary(self):
""" Test mozinstall's get_binary method """
@ -46,13 +48,13 @@ class TestMozInstall(unittest.TestCase):
os.path.join(self.tempdir, 'exe'))
binary_exe = os.path.join(installdir_exe, 'core', 'firefox.exe')
self.assertEqual(binary_exe, mozinstall.get_binary(installdir_exe,
'firefox'))
'firefox'))
installdir_zip = mozinstall.install(self.zipfile,
os.path.join(self.tempdir, 'zip'))
binary_zip = os.path.join(installdir_zip, 'firefox.exe')
self.assertEqual(binary_zip, mozinstall.get_binary(installdir_zip,
'firefox'))
'firefox'))
elif mozinfo.isMac:
installdir = mozinstall.install(self.dmg, self.tempdir)
@ -67,7 +69,8 @@ class TestMozInstall(unittest.TestCase):
tempdir_empty, 'firefox')
mozfile.rmtree(tempdir_empty)
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe for mozinstall 1.12 and higher.")
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
"for mozinstall 1.12 and higher.")
def test_is_installer(self):
""" Test we can identify a correct installer """
@ -84,7 +87,7 @@ class TestMozInstall(unittest.TestCase):
try:
# test stub browser file
# without pefile on the system this test will fail
import pefile
import pefile # noqa
stub_exe = os.path.join(here, 'build_stub', 'firefox.exe')
self.assertFalse(mozinstall.is_installer(stub_exe))
except ImportError:
@ -108,7 +111,8 @@ class TestMozInstall(unittest.TestCase):
self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
self.bz2, 'firefox')
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe for mozinstall 1.12 and higher.")
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
"for mozinstall 1.12 and higher.")
def test_install(self):
""" Test mozinstall's install capability """
@ -132,7 +136,8 @@ class TestMozInstall(unittest.TestCase):
self.assertEqual(os.path.join(os.path.realpath(self.tempdir),
'FirefoxStub.app'), installdir)
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe for mozinstall 1.12 and higher.")
@unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
"for mozinstall 1.12 and higher.")
def test_uninstall(self):
""" Test mozinstall's uninstall capabilites """
# Uninstall after installing

View File

@ -7,3 +7,5 @@ mozleak is a library for extracting memory leaks from leak logs files.
"""
from .leaklog import process_leak_log
__all__ = ['process_leak_log']

View File

@ -5,10 +5,6 @@
import os
import re
import sys
import mozinfo
import mozrunner.utils
def _get_default_logger():
@ -71,8 +67,9 @@ def process_single_leak_file(leakLogFileName, processType, leakThreshold,
# Multiple default processes can end up writing their bloat views into a single
# log, particularly on B2G. Eventually, these should be split into multiple
# logs (bug 1068869), but for now, we report the largest leak.
if totalBytesLeaked != None:
leakAnalysis.append("WARNING | leakcheck | %s multiple BloatView byte totals found"
if totalBytesLeaked is not None:
leakAnalysis.append("WARNING | leakcheck | %s "
"multiple BloatView byte totals found"
% processString)
else:
totalBytesLeaked = 0
@ -194,7 +191,7 @@ def process_leak_log(leak_log_file, leak_thresholds=None,
% (processType, leakThresholds.get(processType, 0)))
for processType in leakThresholds:
if not processType in knownProcessTypes:
if processType not in knownProcessTypes:
log.info("TEST-UNEXPECTED-FAIL | leakcheck | Unknown process type %s in leakThresholds"
% processType)
@ -213,7 +210,7 @@ def process_leak_log(leak_log_file, leak_thresholds=None,
processType = m.group(1)
else:
processType = "default"
if not processType in knownProcessTypes:
if processType not in knownProcessTypes:
log.info("TEST-UNEXPECTED-FAIL | leakcheck | Leak log with unknown process type %s"
% processType)
leakThreshold = leakThresholds.get(processType, 0)

View File

@ -14,7 +14,7 @@ setup(
version=PACKAGE_VERSION,
description="Library for extracting memory leaks from leak logs files",
long_description="see http://mozbase.readthedocs.org/",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Mozilla Automation and Tools team',
author_email='tools@lists.mozilla.org',

View File

@ -24,3 +24,7 @@ from .proxy import get_proxy_logger
# Backwards compatibility shim for consumers that use mozlog.structured
structured = sys.modules[__name__]
sys.modules['{}.structured'.format(__name__)] = structured
__all__ = ['commandline', 'structuredlog', 'unstructured',
'get_default_logger', 'set_default_logger', 'get_proxy_logger',
'structured']

View File

@ -25,17 +25,21 @@ log_formatters = {
TEXT_FORMATTERS = ('raw', 'mach')
"""a subset of formatters for non test harnesses related applications"""
def level_filter_wrapper(formatter, level):
return handlers.LogLevelFilter(formatter, level)
def verbose_wrapper(formatter, verbose):
formatter.verbose = verbose
return formatter
def compact_wrapper(formatter, compact):
formatter.compact = compact
return formatter
def buffer_handler_wrapper(handler, buffer_limit):
if buffer_limit == "UNLIMITED":
buffer_limit = None
@ -43,9 +47,11 @@ def buffer_handler_wrapper(handler, buffer_limit):
buffer_limit = int(buffer_limit)
return handlers.BufferHandler(handler, buffer_limit)
def valgrind_handler_wrapper(handler):
return handlers.ValgrindHandler(handler)
def default_formatter_options(log_type, overrides):
formatter_option_defaults = {
"raw": {
@ -71,7 +77,8 @@ fmt_options = {
"Enables compact mode for the given formatter.",
["tbpl"], "store_true"),
'level': (level_filter_wrapper,
"A least log level to subscribe to for the given formatter (debug, info, error, etc.)",
"A least log level to subscribe to for the given formatter "
"(debug, info, error, etc.)",
["mach", "raw", "tbpl"], "store"),
'buffer': (buffer_handler_wrapper,
"If specified, enables message buffering at the given buffer size limit.",
@ -134,8 +141,8 @@ def add_logging_group(parser, include_formatters=None):
group_add("--log-" + name, action="append", type=opt_log_type,
help=help_str)
for optname, (cls, help_str, formatters, action) in fmt_options.iteritems():
for fmt in formatters:
for optname, (cls, help_str, formatters_, action) in fmt_options.iteritems():
for fmt in formatters_:
# make sure fmt is in log_formatters and is accepted
if fmt in log_formatters and fmt in include_formatters:
group_add("--log-%s-%s" % (fmt, optname), action=action,
@ -182,7 +189,8 @@ def setup_handlers(logger, formatters, formatter_options, allow_unused_options=F
logger.add_handler(handler)
def setup_logging(logger, args, defaults=None, formatter_defaults=None, allow_unused_options=False):
def setup_logging(logger, args, defaults=None, formatter_defaults=None,
allow_unused_options=False):
"""
Configure a structuredlogger based on command line arguments.
@ -250,7 +258,7 @@ def setup_logging(logger, args, defaults=None, formatter_defaults=None, allow_un
formatter_defaults)
formatter_options[formatter][opt] = values
#If there is no user-specified logging, go with the default options
# If there is no user-specified logging, go with the default options
if not found:
for name, value in defaults.iteritems():
formatters[name].append(value)

View File

@ -17,3 +17,7 @@ except ImportError:
def JSONFormatter():
return lambda x: json.dumps(x) + "\n"
__all__ = ['UnittestFormatter', 'XUnitFormatter', 'HTMLFormatter',
'MachFormatter', 'TbplFormatter', 'ErrorSummaryFormatter',
'JSONFormatter']

Some files were not shown because too many files have changed in this diff Show More