mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
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:
parent
2d25ee552e
commit
f45ed99748
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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/.
|
||||
|
@ -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:])
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
""",
|
||||
)
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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, {}))
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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 *
|
||||
|
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -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()
|
||||
|
@ -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/.
|
||||
|
@ -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',
|
||||
]
|
||||
]
|
||||
|
@ -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: -*-
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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']
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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}])
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -23,6 +23,7 @@ SCHEMA = {'Registration Server': (('IPAddr', ''),
|
||||
('ENCR', ''),
|
||||
('EAP', ''))}
|
||||
|
||||
|
||||
def get_cfg(d, ini_path):
|
||||
cfg = ConfigParser.RawConfigParser()
|
||||
try:
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -7,6 +7,7 @@ from StringIO import StringIO
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class ExecTestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -8,6 +8,7 @@ from StringIO import StringIO
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class ExecEnvTestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class InfoTestCase(DeviceManagerTestCase):
|
||||
|
||||
runs_on_test_device = False
|
||||
|
@ -7,6 +7,7 @@ import socket
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class PromptTestCase(DeviceManagerTestCase):
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -9,6 +9,7 @@ import posixpath
|
||||
from dmunit import DeviceManagerTestCase
|
||||
from mozdevice.devicemanager import DMError
|
||||
|
||||
|
||||
class PullTestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -7,6 +7,7 @@ import posixpath
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class Push1TestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -7,6 +7,7 @@ import posixpath
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class Push2TestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -7,6 +7,7 @@ import posixpath
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class PushBinaryTestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -7,6 +7,7 @@ import posixpath
|
||||
|
||||
from dmunit import DeviceManagerTestCase
|
||||
|
||||
|
||||
class PushSmallTextTestCase(DeviceManagerTestCase):
|
||||
|
||||
def runTest(self):
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
#/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
import mozdevice
|
||||
import logging
|
||||
import unittest
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -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()
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
import mozdevice
|
||||
import logging
|
||||
import re
|
||||
|
@ -1,4 +1,4 @@
|
||||
#/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
import mozdevice
|
||||
import logging
|
||||
import unittest
|
||||
|
@ -1,4 +1,4 @@
|
||||
#/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
import mozdevice
|
||||
import logging
|
||||
import unittest
|
||||
|
@ -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)
|
||||
|
@ -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'])
|
||||
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -2,6 +2,7 @@ from sut import MockAgent
|
||||
import mozdevice
|
||||
import unittest
|
||||
|
||||
|
||||
class PsTest(unittest.TestCase):
|
||||
|
||||
pscommands = [('ps',
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
import mozdevice
|
||||
import logging
|
||||
import unittest
|
||||
|
@ -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/.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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"""
|
||||
|
@ -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()
|
||||
|
@ -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']
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
@ -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/.
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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/.
|
||||
|
@ -340,4 +340,3 @@ def uninstall_cli(argv=sys.argv[1:]):
|
||||
|
||||
# Run it
|
||||
uninstall(argv[0])
|
||||
|
||||
|
@ -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="""
|
||||
|
@ -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
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user