From 4e5b7b41a5a4c4d6898ebc8670327d7739361754 Mon Sep 17 00:00:00 2001 From: Malini Das Date: Wed, 11 Dec 2013 07:45:19 -0500 Subject: [PATCH] Bug 925398 - Refactor runtests.py to follow Mixin pattern, r=jgriffin --- .../marionette/client/marionette/__init__.py | 7 +- .../client/marionette/runner/__init__.py | 6 + .../client/marionette/runner/base.py | 923 ++++++++++++++++++ .../marionette/runner/mixins/__init__.py | 7 + .../client/marionette/runner/mixins/b2g.py | 29 + .../marionette/runner/mixins/endurance.py | 196 ++++ .../marionette/runner/mixins/reporting.py | 205 ++++ .../mixins/resources/htmlreport/jquery.js | 2 + .../mixins/resources/htmlreport/main.js | 109 +++ .../mixins/resources/htmlreport/style.css | 158 +++ .../marionette/client/marionette/runtests.py | 4 +- testing/marionette/client/setup.py | 4 +- 12 files changed, 1642 insertions(+), 8 deletions(-) create mode 100644 testing/marionette/client/marionette/runner/__init__.py create mode 100644 testing/marionette/client/marionette/runner/base.py create mode 100644 testing/marionette/client/marionette/runner/mixins/__init__.py create mode 100644 testing/marionette/client/marionette/runner/mixins/b2g.py create mode 100644 testing/marionette/client/marionette/runner/mixins/endurance.py create mode 100644 testing/marionette/client/marionette/runner/mixins/reporting.py create mode 100644 testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js create mode 100644 testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js create mode 100644 testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css diff --git a/testing/marionette/client/marionette/__init__.py b/testing/marionette/client/marionette/__init__.py index 8aae409196b..e3f1f8d43c0 100644 --- a/testing/marionette/client/marionette/__init__.py +++ b/testing/marionette/client/marionette/__init__.py @@ -5,10 +5,7 @@ from gestures import * from by import By from marionette import Marionette, HTMLElement, Actions, MultiActions -from marionette_test import MarionetteTestCase, CommonTestCase, expectedFailure, skip, SkipTest +from marionette_test import MarionetteTestCase, MarionetteJSTestCase, CommonTestCase, expectedFailure, skip, SkipTest from emulator import Emulator -from runtests import MarionetteTestResult -from runtests import MarionetteTestRunner -from runtests import MarionetteTestOptions -from runtests import MarionetteTextTestRunner from errors import * +from runner import * diff --git a/testing/marionette/client/marionette/runner/__init__.py b/testing/marionette/client/marionette/runner/__init__.py new file mode 100644 index 00000000000..ee34308330d --- /dev/null +++ b/testing/marionette/client/marionette/runner/__init__.py @@ -0,0 +1,6 @@ +# 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/. + +from base import * +from mixins import * diff --git a/testing/marionette/client/marionette/runner/base.py b/testing/marionette/client/marionette/runner/base.py new file mode 100644 index 00000000000..aaf6ed4f566 --- /dev/null +++ b/testing/marionette/client/marionette/runner/base.py @@ -0,0 +1,923 @@ +# 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/. + +from optparse import OptionParser +from datetime import datetime +import logging +import os +import unittest +import socket +import sys +import time +import traceback +import random +import moznetwork +import xml.dom.minidom as dom + +from manifestparser import TestManifest +from mozhttpd import MozHttpd +from marionette import Marionette +from moztest.results import TestResultCollection, TestResult, relevant_line + + +class MarionetteTest(TestResult): + + @property + def test_name(self): + if self.test_class is not None: + return '%s.py %s.%s' % (self.test_class.split('.')[0], + self.test_class, + self.name) + else: + return self.name + + +class MarionetteTestResult(unittest._TextTestResult, TestResultCollection): + + resultClass = MarionetteTest + + def __init__(self, *args, **kwargs): + self.marionette = kwargs.pop('marionette') + TestResultCollection.__init__(self, 'MarionetteTest') + unittest._TextTestResult.__init__(self, *args, **kwargs) + self.passed = 0 + self.testsRun = 0 + self.result_modifiers = [] # used by mixins to modify the result + + @property + def skipped(self): + return [t for t in self if t.result == 'SKIPPED'] + + @skipped.setter + def skipped(self, value): + pass + + @property + def expectedFailures(self): + return [t for t in self if t.result == 'KNOWN-FAIL'] + + @expectedFailures.setter + def expectedFailures(self, value): + pass + + @property + def unexpectedSuccesses(self): + return [t for t in self if t.result == 'UNEXPECTED-PASS'] + + @unexpectedSuccesses.setter + def unexpectedSuccesses(self, value): + pass + + @property + def tests_passed(self): + return [t for t in self if t.result == 'PASS'] + + @property + def errors(self): + return [t for t in self if t.result == 'ERROR'] + + @errors.setter + def errors(self, value): + pass + + @property + def failures(self): + return [t for t in self if t.result == 'UNEXPECTED-FAIL'] + + @failures.setter + def failures(self, value): + pass + + @property + def duration(self): + if self.stop_time: + return self.stop_time - self.start_time + else: + return 0 + + def add_test_result(self, test, result_expected='PASS', + result_actual='PASS', output='', context=None, **kwargs): + def get_class(test): + return test.__class__.__module__ + '.' + test.__class__.__name__ + + name = str(test).split()[0] + test_class = get_class(test) + if hasattr(test, 'jsFile'): + name = os.path.basename(test.jsFile) + test_class = None + + t = self.resultClass(name=name, test_class=test_class, + time_start=test.start_time, result_expected=result_expected, + context=context, **kwargs) + # call any registered result modifiers + for modifier in self.result_modifiers: + modifier(t, result_expected, result_actual, output, context) + t.finish(result_actual, + time_end=time.time() if test.start_time else 0, + reason=relevant_line(output), + output=output) + self.append(t) + + def addError(self, test, err): + self.add_test_result(test, output=self._exc_info_to_string(err, test), result_actual='ERROR') + self._mirrorOutput = True + if self.showAll: + self.stream.writeln("ERROR") + elif self.dots: + self.stream.write('E') + self.stream.flush() + + def addFailure(self, test, err): + self.add_test_result(test, output=self._exc_info_to_string(err, test), result_actual='UNEXPECTED-FAIL') + self._mirrorOutput = True + if self.showAll: + self.stream.writeln("FAIL") + elif self.dots: + self.stream.write('F') + self.stream.flush() + + def addSuccess(self, test): + self.passed += 1 + self.add_test_result(test, result_actual='PASS') + if self.showAll: + self.stream.writeln("ok") + elif self.dots: + self.stream.write('.') + self.stream.flush() + + def addExpectedFailure(self, test, err): + """Called when an expected failure/error occured.""" + self.add_test_result(test, output=self._exc_info_to_string(err, test), + result_actual='KNOWN-FAIL') + if self.showAll: + self.stream.writeln("expected failure") + elif self.dots: + self.stream.write("x") + self.stream.flush() + + def addUnexpectedSuccess(self, test): + """Called when a test was expected to fail, but succeed.""" + self.add_test_result(test, result_actual='UNEXPECTED-PASS') + if self.showAll: + self.stream.writeln("unexpected success") + elif self.dots: + self.stream.write("u") + self.stream.flush() + + def addSkip(self, test, reason): + self.add_test_result(test, output=reason, result_actual='SKIPPED') + if self.showAll: + self.stream.writeln("skipped {0!r}".format(reason)) + elif self.dots: + self.stream.write("s") + self.stream.flush() + + def getInfo(self, test): + return test.test_name + + def getDescription(self, test): + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((str(test), doc_first_line)) + else: + desc = str(test) + if hasattr(test, 'jsFile'): + desc = "%s, %s" % (test.jsFile, desc) + return desc + + def printLogs(self, test): + for testcase in test._tests: + if hasattr(testcase, 'loglines') and testcase.loglines: + # Don't dump loglines to the console if they only contain + # TEST-START and TEST-END. + skip_log = True + for line in testcase.loglines: + str_line = ' '.join(line) + if not 'TEST-END' in str_line and not 'TEST-START' in str_line: + skip_log = False + break + if skip_log: + return + self.stream.writeln('\nSTART LOG:') + for line in testcase.loglines: + self.stream.writeln(' '.join(line).encode('ascii', 'replace')) + self.stream.writeln('END LOG:') + + def printErrorList(self, flavour, errors): + for error in errors: + err = error.output + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour, error.description)) + self.stream.writeln(self.separator2) + lastline = None + fail_present = None + for line in err: + if not line.startswith('\t'): + lastline = line + if 'TEST-UNEXPECTED-FAIL' in line: + fail_present = True + for line in err: + if line != lastline or fail_present: + self.stream.writeln("%s" % line) + else: + self.stream.writeln("TEST-UNEXPECTED-FAIL | %s | %s" % + (self.getInfo(error), line)) + + def stopTest(self, *args, **kwargs): + unittest._TextTestResult.stopTest(self, *args, **kwargs) + if self.marionette.check_for_crash(): + # this tells unittest.TestSuite not to continue running tests + self.shouldStop = True + + +class MarionetteTextTestRunner(unittest.TextTestRunner): + + resultclass = MarionetteTestResult + + def __init__(self, **kwargs): + self.marionette = kwargs['marionette'] + del kwargs['marionette'] + unittest.TextTestRunner.__init__(self, **kwargs) + + def _makeResult(self): + return self.resultclass(self.stream, + self.descriptions, + self.verbosity, + marionette=self.marionette) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + if hasattr(self, 'failfast'): + result.failfast = self.failfast + if hasattr(self, 'buffer'): + result.buffer = self.buffer + startTime = time.time() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + try: + test(result) + finally: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + stopTime = time.time() + if hasattr(result, 'time_taken'): + result.time_taken = stopTime - startTime + result.printLogs(test) + result.printErrors() + if hasattr(result, 'separator2'): + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", result.time_taken)) + self.stream.writeln() + + expectedFails = unexpectedSuccesses = skipped = 0 + try: + results = map(len, (result.expectedFailures, + result.unexpectedSuccesses, + result.skipped)) + except AttributeError: + pass + else: + expectedFails, unexpectedSuccesses, skipped = results + + infos = [] + if not result.wasSuccessful(): + self.stream.write("FAILED") + failed, errored = map(len, (result.failures, result.errors)) + if failed: + infos.append("failures=%d" % failed) + if errored: + infos.append("errors=%d" % errored) + else: + self.stream.write("OK") + if skipped: + infos.append("skipped=%d" % skipped) + if expectedFails: + infos.append("expected failures=%d" % expectedFails) + if unexpectedSuccesses: + infos.append("unexpected successes=%d" % unexpectedSuccesses) + if infos: + self.stream.writeln(" (%s)" % (", ".join(infos),)) + else: + self.stream.write("\n") + return result + + +class BaseMarionetteOptions(OptionParser): + def __init__(self, **kwargs): + OptionParser.__init__(self, **kwargs) + self.parse_args_handlers = [] # Used by mixins + self.verify_usage_handlers = [] # Used by mixins + self.add_option('--autolog', + action='store_true', + dest='autolog', + default=False, + help='send test results to autolog') + self.add_option('--revision', + action='store', + dest='revision', + help='git revision for autolog submissions') + self.add_option('--testgroup', + action='store', + dest='testgroup', + help='testgroup names for autolog submissions') + self.add_option('--emulator', + action='store', + dest='emulator', + choices=['x86', 'arm'], + help='if no --address is given, then the harness will launch a B2G emulator on which to run ' + 'emulator tests. if --address is given, then the harness assumes you are running an ' + 'emulator already, and will run the emulator tests using that emulator. you need to ' + 'specify which architecture to emulate for both cases') + self.add_option('--emulator-binary', + action='store', + dest='emulatorBinary', + help='launch a specific emulator binary rather than launching from the B2G built emulator') + self.add_option('--emulator-img', + action='store', + dest='emulatorImg', + help='use a specific image file instead of a fresh one') + self.add_option('--emulator-res', + action='store', + dest='emulator_res', + type='str', + help='set a custom resolution for the emulator' + 'Example: "480x800"') + self.add_option('--sdcard', + action='store', + dest='sdcard', + help='size of sdcard to create for the emulator') + self.add_option('--no-window', + action='store_true', + dest='noWindow', + default=False, + help='when Marionette launches an emulator, start it with the -no-window argument') + self.add_option('--logcat-dir', + dest='logcat_dir', + action='store', + help='directory to store logcat dump files') + self.add_option('--address', + dest='address', + action='store', + help='host:port of running Gecko instance to connect to') + self.add_option('--device', + dest='device_serial', + action='store', + help='serial ID of a device to use for adb / fastboot') + self.add_option('--type', + dest='type', + action='store', + default='browser+b2g', + help="the type of test to run, can be a combination of values defined in the manifest file; " + "individual values are combined with '+' or '-' characters. for example: 'browser+b2g' " + "means the set of tests which are compatible with both browser and b2g; 'b2g-qemu' means " + "the set of tests which are compatible with b2g but do not require an emulator. this " + "argument is only used when loading tests from manifest files") + self.add_option('--homedir', + dest='homedir', + action='store', + help='home directory of emulator files') + self.add_option('--app', + dest='app', + action='store', + help='application to use') + self.add_option('--app-arg', + dest='app_args', + action='append', + default=[], + help='specify a command line argument to be passed onto the application') + self.add_option('--binary', + dest='bin', + action='store', + help='gecko executable to launch before running the test') + self.add_option('--profile', + dest='profile', + action='store', + help='profile to use when launching the gecko process. if not passed, then a profile will be ' + 'constructed and used') + self.add_option('--repeat', + dest='repeat', + action='store', + type=int, + default=0, + help='number of times to repeat the test(s)') + self.add_option('-x', '--xml-output', + action='store', + dest='xml_output', + help='xml output') + self.add_option('--gecko-path', + dest='gecko_path', + action='store', + help='path to b2g gecko binaries that should be installed on the device or emulator') + self.add_option('--testvars', + dest='testvars', + action='store', + help='path to a json file with any test data required') + self.add_option('--tree', + dest='tree', + action='store', + default='b2g', + help='the tree that the revision parameter refers to') + self.add_option('--symbols-path', + dest='symbols_path', + action='store', + help='absolute path to directory containing breakpad symbols, or the url of a zip file containing symbols') + self.add_option('--timeout', + dest='timeout', + type=int, + help='if a --timeout value is given, it will set the default page load timeout, search timeout and script timeout to the given value. If not passed in, it will use the default values of 30000ms for page load, 0ms for search timeout and 10000ms for script timeout') + self.add_option('--es-server', + dest='es_servers', + action='append', + help='the ElasticSearch server to use for autolog submission') + self.add_option('--shuffle', + action='store_true', + dest='shuffle', + default=False, + help='run tests in a random order') + + def parse_args(self, args=None, values=None): + options, tests = OptionParser.parse_args(self, args, values) + for handler in self.parse_args_handlers: + handler(options, tests, args, values) + + return (options, tests) + + def verify_usage(self, options, tests): + if not tests: + print 'must specify one or more test files, manifests, or directories' + sys.exit(1) + + if not options.emulator and not options.address and not options.bin: + print 'must specify --binary, --emulator or --address' + sys.exit(1) + + if not options.es_servers: + options.es_servers = ['elasticsearch-zlb.dev.vlan81.phx.mozilla.com:9200', + 'elasticsearch-zlb.webapp.scl3.mozilla.com:9200'] + + # default to storing logcat output for emulator runs + if options.emulator and not options.logcat_dir: + options.logcat_dir = 'logcat' + + # check for valid resolution string, strip whitespaces + try: + if options.emulator_res: + dims = options.emulator_res.split('x') + assert len(dims) == 2 + width = str(int(dims[0])) + height = str(int(dims[1])) + options.emulator_res = 'x'.join([width, height]) + except: + raise ValueError('Invalid emulator resolution format. ' + 'Should be like "480x800".') + + for handler in self.verify_usage_handlers: + handler(options, tests) + + return (options, tests) + + +class BaseMarionetteTestRunner(object): + + textrunnerclass = MarionetteTextTestRunner + + def __init__(self, address=None, emulator=None, emulatorBinary=None, + emulatorImg=None, emulator_res='480x800', homedir=None, + app=None, app_args=None, bin=None, profile=None, autolog=False, + revision=None, logger=None, testgroup="marionette", noWindow=False, + logcat_dir=None, xml_output=None, repeat=0, gecko_path=None, + testvars=None, tree=None, type=None, device_serial=None, + symbols_path=None, timeout=None, es_servers=None, shuffle=False, + sdcard=None, **kwargs): + self.address = address + self.emulator = emulator + self.emulatorBinary = emulatorBinary + self.emulatorImg = emulatorImg + self.emulator_res = emulator_res + self.homedir = homedir + self.app = app + self.app_args = app_args or [] + self.bin = bin + self.profile = profile + self.autolog = autolog + self.testgroup = testgroup + self.revision = revision + self.logger = logger + self.noWindow = noWindow + self.httpd = None + self.baseurl = None + self.marionette = None + self.logcat_dir = logcat_dir + self.xml_output = xml_output + self.repeat = repeat + self.gecko_path = gecko_path + self.testvars = {} + self.test_kwargs = kwargs + self.tree = tree + self.type = type + self.device_serial = device_serial + self.symbols_path = symbols_path + self.timeout = timeout + self._device = None + self._capabilities = None + self._appName = None + self.es_servers = es_servers + self.shuffle = shuffle + self.sdcard = sdcard + self.mixin_run_tests = [] + + if testvars: + if not os.path.exists(testvars): + raise Exception('--testvars file does not exist') + + import json + with open(testvars) as f: + self.testvars = json.loads(f.read()) + + # set up test handlers + self.test_handlers = [] + + self.reset_test_stats() + + if self.logger is None: + self.logger = logging.getLogger('Marionette') + self.logger.setLevel(logging.INFO) + self.logger.addHandler(logging.StreamHandler()) + + if self.logcat_dir: + if not os.access(self.logcat_dir, os.F_OK): + os.mkdir(self.logcat_dir) + + # for XML output + self.testvars['xml_output'] = self.xml_output + self.results = [] + + @property + def capabilities(self): + if self._capabilities: + return self._capabilities + + self.marionette.start_session() + self._capabilities = self.marionette.session_capabilities + self.marionette.delete_session() + return self._capabilities + + @property + def device(self): + if self._device: + return self._device + + self._device = self.capabilities.get('device') + return self._device + + @property + def appName(self): + if self._appName: + return self._appName + + self._appName = self.capabilities.get('browserName') + return self._appName + + def reset_test_stats(self): + self.passed = 0 + self.failed = 0 + self.todo = 0 + self.failures = [] + + def start_httpd(self): + host = moznetwork.get_ip() + self.httpd = MozHttpd(host=host, + port=0, + docroot=os.path.join(os.path.dirname(os.path.dirname(__file__)), 'www')) + self.httpd.start() + self.baseurl = 'http://%s:%d/' % (host, self.httpd.httpd.server_port) + self.logger.info('running webserver on %s' % self.baseurl) + + def start_marionette(self): + assert(self.baseurl is not None) + if self.bin: + if self.address: + host, port = self.address.split(':') + else: + host = 'localhost' + port = 2828 + self.marionette = Marionette(host=host, + port=int(port), + app=self.app, + app_args=self.app_args, + bin=self.bin, + profile=self.profile, + baseurl=self.baseurl, + timeout=self.timeout, + device_serial=self.device_serial) + elif self.address: + host, port = self.address.split(':') + try: + #establish a socket connection so we can vertify the data come back + connection = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + connection.connect((host,int(port))) + connection.close() + except Exception, e: + raise Exception("Could not connect to given marionette host:port: %s" % e) + if self.emulator: + self.marionette = Marionette.getMarionetteOrExit( + host=host, port=int(port), + connectToRunningEmulator=True, + homedir=self.homedir, + baseurl=self.baseurl, + logcat_dir=self.logcat_dir, + gecko_path=self.gecko_path, + symbols_path=self.symbols_path, + timeout=self.timeout) + else: + self.marionette = Marionette(host=host, + port=int(port), + baseurl=self.baseurl, + timeout=self.timeout) + elif self.emulator: + self.marionette = Marionette.getMarionetteOrExit( + emulator=self.emulator, + emulatorBinary=self.emulatorBinary, + emulatorImg=self.emulatorImg, + emulator_res=self.emulator_res, + homedir=self.homedir, + baseurl=self.baseurl, + noWindow=self.noWindow, + logcat_dir=self.logcat_dir, + gecko_path=self.gecko_path, + symbols_path=self.symbols_path, + timeout=self.timeout, + sdcard=self.sdcard) + else: + raise Exception("must specify binary, address or emulator") + + def post_to_autolog(self, elapsedtime): + self.logger.info('posting results to autolog') + + logfile = None + if self.emulator: + filename = os.path.join(os.path.abspath(self.logcat_dir), + "emulator-%d.log" % self.marionette.emulator.port) + if os.access(filename, os.F_OK): + logfile = filename + + for es_server in self.es_servers: + + # This is all autolog stuff. + # See: https://wiki.mozilla.org/Auto-tools/Projects/Autolog + from mozautolog import RESTfulAutologTestGroup + testgroup = RESTfulAutologTestGroup( + testgroup=self.testgroup, + os='android', + platform='emulator', + harness='marionette', + server=es_server, + restserver=None, + machine=socket.gethostname(), + logfile=logfile) + + testgroup.set_primary_product( + tree=self.tree, + buildtype='opt', + revision=self.revision) + + testgroup.add_test_suite( + testsuite='b2g emulator testsuite', + elapsedtime=elapsedtime.seconds, + cmdline='', + passed=self.passed, + failed=self.failed, + todo=self.todo) + + # Add in the test failures. + for f in self.failures: + testgroup.add_test_failure(test=f[0], text=f[1], status=f[2]) + + testgroup.submit() + + def run_tests(self, tests): + self.reset_test_stats() + starttime = datetime.utcnow() + counter = self.repeat + while counter >=0: + round = self.repeat - counter + if round > 0: + self.logger.info('\nREPEAT %d\n-------' % round) + if self.shuffle: + random.shuffle(tests) + for test in tests: + self.run_test(test) + counter -= 1 + self.logger.info('\nSUMMARY\n-------') + self.logger.info('passed: %d' % self.passed) + self.logger.info('failed: %d' % self.failed) + self.logger.info('todo: %d' % self.todo) + + if self.failed > 0: + self.logger.info('\nFAILED TESTS\n-------') + for failed_test in self.failures: + self.logger.info('%s' % failed_test[0]) + + try: + self.marionette.check_for_crash() + except: + traceback.print_exc() + + self.elapsedtime = datetime.utcnow() - starttime + if self.autolog: + self.post_to_autolog(self.elapsedtime) + + if self.xml_output: + xml_dir = os.path.dirname(os.path.abspath(self.xml_output)) + if not os.path.exists(xml_dir): + os.makedirs(xml_dir) + with open(self.xml_output, 'w') as f: + f.write(self.generate_xml(self.results)) + + if self.marionette.instance: + self.marionette.instance.close() + self.marionette.instance = None + del self.marionette + for run_tests in self.mixin_run_tests: + run_tests(tests) + + def run_test(self, test, expected='pass'): + if not self.httpd: + print "starting httpd" + self.start_httpd() + + if not self.marionette: + self.start_marionette() + if self.emulator: + self.marionette.emulator.wait_for_homescreen(self.marionette) + + testargs = {} + if self.type is not None: + testtypes = self.type.replace('+', ' +').replace('-', ' -').split() + for atype in testtypes: + if atype.startswith('+'): + testargs.update({ atype[1:]: 'true' }) + elif atype.startswith('-'): + testargs.update({ atype[1:]: 'false' }) + else: + testargs.update({ atype: 'true' }) + oop = testargs.get('oop', False) + if isinstance(oop, basestring): + oop = False if oop == 'false' else 'true' + + filepath = os.path.abspath(test) + + if os.path.isdir(filepath): + for root, dirs, files in os.walk(filepath): + if self.shuffle: + random.shuffle(files) + for filename in files: + if ((filename.startswith('test_') or filename.startswith('browser_')) and + (filename.endswith('.py') or filename.endswith('.js'))): + filepath = os.path.join(root, filename) + self.run_test(filepath) + if self.marionette.check_for_crash(): + return + return + + mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1]) + + testloader = unittest.TestLoader() + suite = unittest.TestSuite() + + if file_ext == '.ini': + manifest = TestManifest() + manifest.read(filepath) + + all_tests = manifest.active_tests(disabled=False) + manifest_tests = manifest.active_tests(disabled=False, + device=self.device, + app=self.appName) + skip_tests = list(set([x['path'] for x in all_tests]) - + set([x['path'] for x in manifest_tests])) + for skipped in skip_tests: + self.logger.info('TEST-SKIP | %s | device=%s, app=%s' % + (os.path.basename(skipped), + self.device, + self.appName)) + self.todo += 1 + + target_tests = manifest.get(tests=manifest_tests, **testargs) + if self.shuffle: + random.shuffle(target_tests) + for i in target_tests: + self.run_test(i["path"], i["expected"]) + if self.marionette.check_for_crash(): + return + return + + self.logger.info('TEST-START %s' % os.path.basename(test)) + + self.test_kwargs['expected'] = expected + self.test_kwargs['oop'] = oop + for handler in self.test_handlers: + if handler.match(os.path.basename(test)): + handler.add_tests_to_suite(mod_name, + filepath, + suite, + testloader, + self.marionette, + self.testvars, + **self.test_kwargs) + break + + if suite.countTestCases(): + runner = self.textrunnerclass(verbosity=3, + marionette=self.marionette) + results = runner.run(suite) + self.results.append(results) + + self.failed += len(results.failures) + len(results.errors) + if hasattr(results, 'skipped'): + self.todo += len(results.skipped) + self.passed += results.passed + for failure in results.failures + results.errors: + self.failures.append((results.getInfo(failure), failure.output, 'TEST-UNEXPECTED-FAIL')) + if hasattr(results, 'unexpectedSuccesses'): + self.failed += len(results.unexpectedSuccesses) + for failure in results.unexpectedSuccesses: + self.failures.append((results.getInfo(failure), 'TEST-UNEXPECTED-PASS')) + if hasattr(results, 'expectedFailures'): + self.passed += len(results.expectedFailures) + + def cleanup(self): + if self.httpd: + self.httpd.stop() + + __del__ = cleanup + + def generate_xml(self, results_list): + + def _extract_xml(test, result='passed'): + testcase = doc.createElement('testcase') + testcase.setAttribute('classname', test.test_class) + testcase.setAttribute('name', unicode(test.name).split()[0]) + testcase.setAttribute('time', str(test.duration)) + testsuite.appendChild(testcase) + + if result in ['failure', 'error', 'skipped']: + f = doc.createElement(result) + f.setAttribute('message', 'test %s' % result) + f.appendChild(doc.createTextNode(test.reason)) + testcase.appendChild(f) + + doc = dom.Document() + + testsuite = doc.createElement('testsuite') + testsuite.setAttribute('name', 'Marionette') + testsuite.setAttribute('time', str(self.elapsedtime.total_seconds())) + testsuite.setAttribute('tests', str(sum([results.testsRun for + results in results_list]))) + + def failed_count(results): + count = len(results.failures) + if hasattr(results, 'unexpectedSuccesses'): + count += len(results.unexpectedSuccesses) + return count + + testsuite.setAttribute('failures', str(sum([failed_count(results) + for results in results_list]))) + testsuite.setAttribute('errors', str(sum([len(results.errors) + for results in results_list]))) + testsuite.setAttribute('skips', str(sum([len(results.skipped) + + len(results.expectedFailures) + for results in results_list]))) + + for results in results_list: + + for result in results.errors: + _extract_xml(result, result='error') + + for result in results.failures: + _extract_xml(result, result='failure') + + if hasattr(results, 'unexpectedSuccesses'): + for test in results.unexpectedSuccesses: + # unexpectedSuccesses is a list of Testcases only, no tuples + _extract_xml(test, result='failure') + + if hasattr(results, 'skipped'): + for result in results.skipped: + _extract_xml(result, result='skipped') + + if hasattr(results, 'expectedFailures'): + for result in results.expectedFailures: + _extract_xml(result, result='skipped') + + for result in results.tests_passed: + _extract_xml(result) + + doc.appendChild(testsuite) + return doc.toprettyxml(encoding='utf-8') + diff --git a/testing/marionette/client/marionette/runner/mixins/__init__.py b/testing/marionette/client/marionette/runner/mixins/__init__.py new file mode 100644 index 00000000000..a3d8714c1f0 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/__init__.py @@ -0,0 +1,7 @@ +# 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/. + +from endurance import * +from reporting import * +from b2g import * diff --git a/testing/marionette/client/marionette/runner/mixins/b2g.py b/testing/marionette/client/marionette/runner/mixins/b2g.py new file mode 100644 index 00000000000..8cbe388d152 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/b2g.py @@ -0,0 +1,29 @@ +# 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/. + +import mozdevice +import os + + +class B2GTestCaseMixin(object): + + # TODO: add methods like 'restart b2g' + def __init__(self, *args, **kwargs): + self._device_manager = None + + @property + def device_manager(self, *args, **kwargs): + if not self._device_manager: + dm_type = os.environ.get('DM_TRANS', 'adb') + if dm_type == 'adb': + self._device_manager = mozdevice.DeviceManagerADB() + elif dm_type == 'sut': + host = os.environ.get('TEST_DEVICE') + if not host: + raise Exception('Must specify host with SUT!') + self._device_manager = mozdevice.DeviceManagerSUT(host=host) + else: + raise Exception('Unknown device manager type: %s' % dm_type) + return self._device_manager + diff --git a/testing/marionette/client/marionette/runner/mixins/endurance.py b/testing/marionette/client/marionette/runner/mixins/endurance.py new file mode 100644 index 00000000000..a618bb930c6 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/endurance.py @@ -0,0 +1,196 @@ +# 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/. + +import os +import sys +import time + + +class EnduranceOptionsMixin(object): + + # parse_args + def endurance_parse_args(self, options, tests, args=None, values=None): + if options.iterations is not None: + if options.checkpoint_interval is None or options.checkpoint_interval > options.iterations: + options.checkpoint_interval = options.iterations + + # verify_usage + def endurance_verify_usage(self, options, tests): + if options.iterations is not None and options.iterations < 1: + raise ValueError('iterations must be a positive integer') + if options.checkpoint_interval is not None and options.checkpoint_interval < 1: + raise ValueError('checkpoint interval must be a positive integer') + if options.checkpoint_interval and not options.iterations: + raise ValueError('you must specify iterations when using checkpoint intervals') + + def __init__(self, **kwargs): + # Inheriting object must call this __init__ to set up option handling + group = self.add_option_group('endurance') + group.add_option('--iterations', + action='store', + dest='iterations', + type='int', + metavar='int', + help='iterations for endurance tests') + group.add_option('--checkpoint', + action='store', + dest='checkpoint_interval', + type='int', + metavar='int', + help='checkpoint interval for endurance tests') + self.parse_args_handlers.append(self.endurance_parse_args) + self.verify_usage_handlers.append(self.endurance_verify_usage) + + +class EnduranceTestCaseMixin(object): + def __init__(self, *args, **kwargs): + self.iterations = kwargs.pop('iterations') or 1 + self.checkpoint_interval = kwargs.pop('checkpoint_interval') or self.iterations + self.drive_setup_functions = [] + self.pre_test_functions = [] + self.post_test_functions = [] + self.checkpoint_functions = [] + self.process_checkpoint_functions = [] + self.log_name = None + self.checkpoint_path = None + + def add_drive_setup_function(self, function): + self.drive_setup_functions.append(function) + + def add_pre_test_function(self, function): + self.pre_test_functions.append(function) + + def add_post_test_function(self, function): + self.post_test_functions.append(function) + + def add_checkpoint_function(self, function): + self.checkpoint_functions.append(function) + + def add_process_checkpoint_function(self, function): + self.process_checkpoint_functions.append(function) + + def drive(self, test, app=None): + self.test_method = test + self.app_under_test = app + for function in self.drive_setup_functions: + function(test, app) + + # Now drive the actual test case iterations + for count in range(1, self.iterations + 1): + self.iteration = count + self.marionette.log("%s iteration %d of %d" % (self.test_method.__name__, count, self.iterations)) + # Print to console so can see what iteration we're on while test is running + if self.iteration == 1: + print "\n" + print "Iteration %d of %d..." % (count, self.iterations) + sys.stdout.flush() + + for function in self.pre_test_functions: + function() + + self.test_method() + + for function in self.post_test_functions: + function() + + # Checkpoint time? + if ((count % self.checkpoint_interval) == 0) or count == self.iterations: + self.checkpoint() + + # Finished, now process checkpoint data into .json output + self.process_checkpoint_data() + + def checkpoint(self): + # Console output so know what's happening if watching console + print "Checkpoint..." + sys.stdout.flush() + self.cur_time = time.strftime("%Y%m%d%H%M%S", time.localtime()) + # If first checkpoint, create the file if it doesn't exist already + if self.iteration in (0, self.checkpoint_interval): + self.checkpoint_path = "checkpoints" + if not os.path.exists(self.checkpoint_path): + os.makedirs(self.checkpoint_path, 0755) + self.log_name = "%s/checkpoint_%s_%s.log" % (self.checkpoint_path, self.test_method.__name__, self.cur_time) + with open(self.log_name, 'a') as log_file: + log_file.write('%s Endurance Test: %s\n' % (self.cur_time, self.test_method.__name__)) + log_file.write('%s Checkpoint after iteration %d of %d:\n' % (self.cur_time, self.iteration, self.iterations)) + else: + with open(self.log_name, 'a') as log_file: + log_file.write('%s Checkpoint after iteration %d of %d:\n' % (self.cur_time, self.iteration, self.iterations)) + + for function in self.checkpoint_functions: + function() + + def process_checkpoint_data(self): + # Process checkpoint data into .json + self.marionette.log("processing checkpoint data") + for function in self.process_checkpoint_functions: + function() + + +class MemoryEnduranceTestCaseMixin(object): + + def __init__(self, *args, **kwargs): + # TODO: add desktop support + if self.device_manager: + self.add_checkpoint_function(self.memory_b2g_checkpoint) + self.add_process_checkpoint_function(self.memory_b2g_process_checkpoint) + + def memory_b2g_checkpoint(self): + # Sleep to give device idle time (for gc) + idle_time = 30 + self.marionette.log("sleeping %d seconds to give the device some idle time" % idle_time) + time.sleep(idle_time) + + # Dump out some memory status info + self.marionette.log("checkpoint") + output_str = self.device_manager.shellCheckOutput(["b2g-ps"]) + with open(self.log_name, 'a') as log_file: + log_file.write('%s\n' % output_str) + + def memory_b2g_process_checkpoint(self): + # Process checkpoint data into .json + self.marionette.log("processing checkpoint data from %s" % self.log_name) + + # Open the checkpoint file + checkpoint_file = open(self.log_name, 'r') + + # Grab every b2g rss reading for each checkpoint + b2g_rss_list = [] + for next_line in checkpoint_file: + if next_line.startswith("b2g"): + b2g_rss_list.append(next_line.split()[5]) + + # Close the checkpoint file + checkpoint_file.close() + + # Calculate the average b2g_rss + total = 0 + for b2g_mem_value in b2g_rss_list: + total += int(b2g_mem_value) + avg_rss = total / len(b2g_rss_list) + + # Create a summary text file + summary_name = self.log_name.replace('.log', '_summary.log') + summary_file = open(summary_name, 'w') + + # Write the summarized checkpoint data + summary_file.write('test_name: %s\n' % self.test_method.__name__) + summary_file.write('completed: %s\n' % self.cur_time) + summary_file.write('app_under_test: %s\n' % self.app_under_test.lower()) + summary_file.write('total_iterations: %d\n' % self.iterations) + summary_file.write('checkpoint_interval: %d\n' % self.checkpoint_interval) + summary_file.write('b2g_rss: ') + summary_file.write(', '.join(b2g_rss_list)) + summary_file.write('\navg_rss: %d\n\n' % avg_rss) + + # Close the summary file + summary_file.close() + + # Write to suite summary file + suite_summary_file_name = '%s/avg_b2g_rss_suite_summary.log' % self.checkpoint_path + suite_summary_file = open(suite_summary_file_name, 'a') + suite_summary_file.write('%s: %s\n' % (self.test_method.__name__, avg_rss)) + suite_summary_file.close() + diff --git a/testing/marionette/client/marionette/runner/mixins/reporting.py b/testing/marionette/client/marionette/runner/mixins/reporting.py new file mode 100644 index 00000000000..a41fbac714a --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/reporting.py @@ -0,0 +1,205 @@ +# 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/. + +import base64 +import cgi +import datetime +import json +import os +import pkg_resources +import sys + +from py.xml import html +from py.xml import raw + + +class HTMLReportingTestRunnerMixin(object): + + def __init__(self, name=None, version=None, html_output=None, **kwargs): + """ + Name should be the name of the name of the testrunner, version should correspond + to the testrunner version. + html_output is the file to output to + """ + # for HTML output + self.html_output = html_output + self.html_name = name + self.html_version = version + self.testvars['html_output'] = self.html_output + self.mixin_run_tests.append(self.html_run_tests) + + def html_run_tests(self, tests): + if self.html_output: + # change default encoding to avoid encoding problem for page source + reload(sys) + sys.setdefaultencoding('utf-8') + html_dir = os.path.dirname(os.path.abspath(self.html_output)) + if not os.path.exists(html_dir): + os.makedirs(html_dir) + with open(self.html_output, 'w') as f: + f.write(self.generate_html(self.results)) + + def generate_html(self, results_list): + + tests = sum([results.testsRun for results in results_list]) + failures = sum([len(results.failures) for results in results_list]) + expected_failures = sum([len(results.expectedFailures) for results in results_list]) + skips = sum([len(results.skipped) for results in results_list]) + errors = sum([len(results.errors) for results in results_list]) + passes = sum([results.passed for results in results_list]) + unexpected_passes = sum([len(results.unexpectedSuccesses) for results in results_list]) + test_time = self.elapsedtime.total_seconds() + test_logs = [] + + def _extract_html(test, class_name, duration=0, text='', result='passed', debug=None): + cls_name = class_name + tc_name = unicode(test) + tc_time = duration + additional_html = [] + debug = debug or {} + links_html = [] + + if result in ['skipped', 'failure', 'expected failure', 'error']: + if debug.get('screenshot'): + screenshot = 'data:image/png;base64,%s' % debug['screenshot'] + additional_html.append(html.div( + html.a(html.img(src=screenshot), href="#"), + class_='screenshot')) + for name, content in debug.items(): + try: + if 'screenshot' in name: + href = '#' + else: + # use base64 to avoid that some browser (such as Firefox, Opera) + # treats '#' as the start of another link if the data URL contains. + # use 'charset=utf-8' to show special characters like Chinese. + href = 'data:text/plain;charset=utf-8;base64,%s' % base64.b64encode(content) + links_html.append(html.a( + name.title(), + class_=name, + href=href, + target='_blank')) + links_html.append(' ') + except: + pass + + log = html.div(class_='log') + for line in text.splitlines(): + separator = line.startswith(' ' * 10) + if separator: + log.append(line[:80]) + else: + if line.lower().find("error") != -1 or line.lower().find("exception") != -1: + log.append(html.span(raw(cgi.escape(line)), class_='error')) + else: + log.append(raw(cgi.escape(line))) + log.append(html.br()) + additional_html.append(log) + + test_logs.append(html.tr([ + html.td(result.title(), class_='col-result'), + html.td(cls_name, class_='col-class'), + html.td(tc_name, class_='col-name'), + html.td(tc_time, class_='col-duration'), + html.td(links_html, class_='col-links'), + html.td(additional_html, class_='debug')], + class_=result.lower() + ' results-table-row')) + + for results in results_list: + for test in results.tests_passed: + _extract_html(test.name, test.test_class) + for result in results.skipped: + _extract_html(result.name, result.test_class, text='\n'.join(result.output), result='skipped') + for result in results.failures: + _extract_html(result.name, result.test_class, text='\n'.join(result.output), result='failure', debug=result.debug) + for result in results.expectedFailures: + _extract_html(result.name, result.test_class, text='\n'.join(result.output), result='expected failure', debug=result.debug) + for test in results.unexpectedSuccesses: + _extract_html(test.name, test.test_class, result='unexpected pass') + for result in results.errors: + _extract_html(result.name, result.test_class, text='\n'.join(result.output), result='error', debug=result.debug) + + generated = datetime.datetime.now() + doc = html.html( + html.head( + html.meta(charset='utf-8'), + html.title('Test Report'), + #TODO: must redisgn this to use marionette's resourcs, instead of the caller folder's + html.style(raw(pkg_resources.resource_string( + __name__, os.path.sep.join(['resources', 'htmlreport', 'style.css']))), + type='text/css')), + html.body( + html.script(raw(pkg_resources.resource_string( + __name__, os.path.sep.join(['resources', 'htmlreport', 'jquery.js']))), + type='text/javascript'), + html.script(raw(pkg_resources.resource_string( + __name__, os.path.sep.join(['resources', 'htmlreport', 'main.js']))), + type='text/javascript'), + html.p('Report generated on %s at %s by %s %s' % ( + generated.strftime('%d-%b-%Y'), + generated.strftime('%H:%M:%S'), + self.html_name, self.html_version)), + html.h2('Summary'), + html.p('%i tests ran in %i seconds.' % (tests, test_time), + html.br(), + html.span('%i passed' % passes, class_='passed'), ', ', + html.span('%i skipped' % skips, class_='skipped'), ', ', + html.span('%i failed' % failures, class_='failed'), ', ', + html.span('%i errors' % errors, class_='error'), '.', + html.br(), + html.span('%i expected failures' % expected_failures, + class_='expected failure'), ', ', + html.span('%i unexpected passes' % unexpected_passes, + class_='unexpected pass'), '.'), + html.h2('Results'), + html.table([html.thead( + html.tr([ + html.th('Result', class_='sortable', col='result'), + html.th('Class', class_='sortable', col='class'), + html.th('Test Name', class_='sortable', col='name'), + html.th('Duration', class_='sortable numeric', col='duration'), + html.th('Links')]), id='results-table-head'), + html.tbody(test_logs, id='results-table-body')], id='results-table'))) + return doc.unicode(indent=2) + + +class HTMLReportingOptionsMixin(object): + + def __init__(self, **kwargs): + group = self.add_option_group('htmlreporting') + group.add_option('--html-output', + action='store', + dest='html_output', + help='html output', + metavar='path') + + +class HTMLReportingTestResultMixin(object): + + def __init__(self, *args, **kwargs): + self.result_modifiers.append(self.html_modifier) + + def html_modifier(self, test, result_expected, result_actual, output, context): + test.debug = None + if result_actual is not 'PASS': + test.debug = self.gather_debug() + + def gather_debug(self): + debug = {} + try: + # TODO make screenshot consistant size by using full viewport + # Bug 883294 - Add ability to take full viewport screenshots + debug['screenshot'] = self.marionette.screenshot() + debug['source'] = self.marionette.page_source + self.marionette.switch_to_frame() + debug['settings'] = json.dumps(self.marionette.execute_async_script(""" +SpecialPowers.addPermission('settings-read', true, document); +var req = window.navigator.mozSettings.createLock().get('*'); +req.onsuccess = function() { + marionetteScriptFinished(req.result); +}""", special_powers=True), sort_keys=True, indent=4, separators=(',', ': ')) + except: + pass + return debug + diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js new file mode 100644 index 00000000000..b827e03c3c4 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
t
",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
","
"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js new file mode 100644 index 00000000000..870d47d4624 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js @@ -0,0 +1,109 @@ +/* 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/. */ + +$(document).ready(function() { + + reset_sort_headers(); + + split_debug_onto_two_rows(); + + $('.col-links a.screenshot').click(function(event) { + window.open($(this).parents('.results-table-row').next('.debug').find('.screenshot img').attr('src')); + event.preventDefault(); + }); + + $('.screenshot a').click(function(event) { + window.open($(this).find('img').attr('src')); + event.preventDefault(); + }); + + $('.sortable').click(toggle_sort_states); + + $('.sortable').click(function() { + var columnName = $(this).attr('col'); + if ($(this).hasClass('numeric')) { + sort_rows_num($(this), 'col-' + columnName); + } else { + sort_rows_alpha($(this), 'col-' + columnName); + } + }); + +}); + +function sort_rows_alpha(clicked, sortclass) { + one_row_for_data(); + var therows = $('.results-table-row'); + therows.sort(function(s, t) { + var a = s.getElementsByClassName(sortclass)[0].innerHTML.toLowerCase(); + var b = t.getElementsByClassName(sortclass)[0].innerHTML.toLowerCase(); + if (clicked.hasClass('asc')) { + if (a < b) + return -1; + if (a > b) + return 1; + return 0; + } else { + if (a < b) + return 1; + if (a > b) + return -1; + return 0; + } + }); + $('#results-table-body').append(therows); + split_debug_onto_two_rows(); +} + +function sort_rows_num(clicked, sortclass) { + one_row_for_data(); + var therows = $('.results-table-row'); + therows.sort(function(s, t) { + var a = s.getElementsByClassName(sortclass)[0].innerHTML + var b = t.getElementsByClassName(sortclass)[0].innerHTML + if (clicked.hasClass('asc')) { + return a - b; + } else { + return b - a; + } + }); + $('#results-table-body').append(therows); + split_debug_onto_two_rows(); +} + +function reset_sort_headers() { + $('.sort-icon').remove(); + $('.sortable').prepend('
vvv
'); + $('.sortable').removeClass('asc desc inactive active'); + $('.sortable').addClass('asc inactive'); +} + +function toggle_sort_states() { + //if active, toggle between asc and desc + if ($(this).hasClass('active')) { + $(this).toggleClass('asc'); + $(this).toggleClass('desc'); + } + + //if inactive, reset all other functions and add ascending active + if ($(this).hasClass('inactive')) { + reset_sort_headers(); + $(this).removeClass('inactive'); + $(this).addClass('active'); + } +} + +function split_debug_onto_two_rows() { + $('tr.results-table-row').each(function() { + $('
').insertAfter(this).append($('.debug', this)); + }); + $('td.debug').attr('colspan', 5); +} + +function one_row_for_data() { + $('tr.results-table-row').each(function() { + if ($(this).next().hasClass('debug')) { + $(this).append($(this).next().contents().unwrap()); + } + }); +} diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css new file mode 100644 index 00000000000..e9bce9500a7 --- /dev/null +++ b/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css @@ -0,0 +1,158 @@ +/* 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/. */ + +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; + min-width: 1200px; + color: #999; +} +h2 { + font-size: 16px; + color: black; +} + +p { + color: black; +} + +a { + color: #999; +} + +table { + border-collapse: collapse; +} + +/****************************** + * SUMMARY INFORMATION + ******************************/ + +#configuration td { + padding: 5px; + border: 1px solid #E6E6E6; +} + +#configuration tr:nth-child(odd) { + background-color: #f6f6f6; +} + +/****************************** + * TEST RESULT COLORS + ******************************/ +span.passed, .passed .col-result { + color: green; +} +span.expected.failure, .expected.failure .col-result { + color: orange; +} +span.skipped, .skipped .col-result { + color: orange; +} +span.unexpected.pass, .unexpected.pass .col-result { + color: red; +} +span.failed, .failure .col-result { + color: red; +} +span.error,.error .col-result { + color: red; +} + + +/****************************** + * RESULTS TABLE + * + * 1. Table Layout + * 2. Debug + * 3. Sorting items + * + ******************************/ + +/*------------------ + * 1. Table Layout + *------------------*/ + +#results-table { + border: 1px solid #e6e6e6; + color: #999; + font-size: 12px; + width: 100% +} + +#results-table th, #results-table td { + padding: 5px; + border: 1px solid #E6E6E6; + text-align: left +} +#results-table th { + font-weight: bold +} + +/*------------------ + * 2. Debug + *------------------*/ + +.log:only-child { + height: inherit +} +.log { + background-color: #e6e6e6; + border: 1px solid #e6e6e6; + color: black; + display: block; + font-family: "Courier New", Courier, monospace; + height: 230px; + overflow-y: scroll; + padding: 5px; + white-space: pre-wrap +} +div.screenshot { + border: 1px solid #e6e6e6; + float: right; + margin-left: 5px; + height: 240px +} +div.screenshot img { + height: 240px +} + +/*if the result is passed or xpassed don't show debug row*/ +.passed + .debug, .unexpected.pass + .debug { + display: none; +} + +/*------------------ + * 3. Sorting items + *------------------*/ +.sortable { + cursor: pointer; +} + +.sort-icon { + font-size: 0px; + float: left; + margin-right: 5px; + margin-top: 5px; + /*triangle*/ + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; +} + +.inactive .sort-icon { + /*finish triangle*/ + border-top: 8px solid #E6E6E6; +} + +.asc.active .sort-icon { + /*finish triangle*/ + border-bottom: 8px solid #999; +} + +.desc.active .sort-icon { + /*finish triangle*/ + border-top: 8px solid #999; +} diff --git a/testing/marionette/client/marionette/runtests.py b/testing/marionette/client/marionette/runtests.py index 0d32d5a42fa..acef9c67649 100644 --- a/testing/marionette/client/marionette/runtests.py +++ b/testing/marionette/client/marionette/runtests.py @@ -917,7 +917,8 @@ def startTestRunner(runner_class, options, tests): runner.run_tests(tests) return runner -def cli(runner_class=MarionetteTestRunner, parser_class=MarionetteTestOptions): + +def cli(runner_class=MarionetteTestRunner, parser_class=BaseMarionetteOptions): parser = parser_class(usage='%prog [options] test_file_or_dir ...') options, tests = parser.parse_args() parser.verify_usage(options, tests) @@ -928,3 +929,4 @@ def cli(runner_class=MarionetteTestRunner, parser_class=MarionetteTestOptions): if __name__ == "__main__": cli() + diff --git a/testing/marionette/client/setup.py b/testing/marionette/client/setup.py index 8cb5c189158..6d8443d8046 100644 --- a/testing/marionette/client/setup.py +++ b/testing/marionette/client/setup.py @@ -1,7 +1,7 @@ import os from setuptools import setup, find_packages -version = '0.6.2' +version = '0.7.0' # get documentation from the README try: @@ -15,7 +15,7 @@ deps = ['manifestdestiny', 'mozhttpd >= 0.5', 'mozprocess >= 0.9', 'mozrunner >= 5.15', 'mozdevice >= 0.22', 'moznetwork >= 0.21', 'mozcrash >= 0.5', 'mozprofile >= 0.7', - 'moztest >= 0.1'] + 'moztest >= 0.1', 'py==1.4.14'] setup(name='marionette_client', version=version,