1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, unicode_literals
6
7import argparse
8import os
9import sys
10
11from mozbuild.base import (
12    MachCommandBase,
13    MachCommandConditions as conditions,
14)
15
16from mach.decorators import (
17    CommandArgument,
18    CommandArgumentGroup,
19    CommandProvider,
20    Command,
21)
22
23def setup_awsy_argument_parser():
24    from marionette_harness.runtests import MarionetteArguments
25    from mozlog.structured import commandline
26
27    parser = MarionetteArguments()
28    commandline.add_logging_group(parser)
29
30    return parser
31
32
33@CommandProvider
34class MachCommands(MachCommandBase):
35    AWSY_PATH = os.path.dirname(os.path.realpath(__file__))
36    if AWSY_PATH not in sys.path:
37        sys.path.append(AWSY_PATH)
38    from awsy import ITERATIONS, PER_TAB_PAUSE, SETTLE_WAIT_TIME, MAX_TABS
39
40    def run_awsy(self, tests, binary=None, **kwargs):
41        import json
42        from mozlog.structured import commandline
43
44        from marionette_harness.runtests import (
45            MarionetteTestRunner,
46            MarionetteHarness
47        )
48
49        parser = setup_awsy_argument_parser()
50
51        awsy_source_dir = os.path.join(self.topsrcdir, 'testing', 'awsy')
52        if not tests:
53            tests = [os.path.join(awsy_source_dir,
54                                  'awsy',
55                                  'test_memory_usage.py')]
56
57        args = argparse.Namespace(tests=tests)
58
59        args.binary = binary
60
61        if kwargs['quick']:
62            kwargs['entities'] = 3
63            kwargs['iterations'] = 1
64            kwargs['perTabPause'] = 1
65            kwargs['settleWaitTime'] = 1
66
67        if 'disable_stylo' in kwargs and kwargs['disable_stylo']:
68            if 'single_stylo_traversal' in kwargs and kwargs['single_stylo_traversal']:
69                print("--disable-stylo conflicts with --single-stylo-traversal")
70                return 1
71            if 'enable_stylo' in kwargs and kwargs['enable_stylo']:
72                print("--disable-stylo conflicts with --enable-stylo")
73                return 1
74
75        if 'single_stylo_traversal' in kwargs and kwargs['single_stylo_traversal']:
76            os.environ['STYLO_THREADS'] = '1'
77        else:
78            os.environ['STYLO_THREADS'] = '4'
79
80        if 'enable_stylo' in kwargs and kwargs['enable_stylo']:
81            os.environ['STYLO_FORCE_ENABLED'] = '1'
82        if 'disable_stylo' in kwargs and kwargs['disable_stylo']:
83            os.environ['STYLO_FORCE_DISABLED'] = '1'
84
85        if 'enable_webrender' in kwargs and kwargs['enable_webrender']:
86            os.environ['MOZ_WEBRENDER'] = '1'
87            os.environ['MOZ_ACCELERATED'] = '1'
88
89        runtime_testvars = {}
90        for arg in ('webRootDir', 'pageManifest', 'resultsDir', 'entities', 'iterations',
91                    'perTabPause', 'settleWaitTime', 'maxTabs', 'dmd'):
92            if kwargs[arg]:
93                runtime_testvars[arg] = kwargs[arg]
94
95        if 'webRootDir' not in runtime_testvars:
96            awsy_tests_dir = os.path.join(self.topobjdir, '_tests', 'awsy')
97            web_root_dir = os.path.join(awsy_tests_dir, 'html')
98            runtime_testvars['webRootDir'] = web_root_dir
99        else:
100            web_root_dir = runtime_testvars['webRootDir']
101            awsy_tests_dir = os.path.dirname(web_root_dir)
102
103        if 'resultsDir' not in runtime_testvars:
104            runtime_testvars['resultsDir'] = os.path.join(awsy_tests_dir,
105                                                          'results')
106        page_load_test_dir = os.path.join(web_root_dir, 'page_load_test')
107        if not os.path.isdir(page_load_test_dir):
108            os.makedirs(page_load_test_dir)
109
110        if not os.path.isdir(runtime_testvars['resultsDir']):
111            os.makedirs(runtime_testvars['resultsDir'])
112
113        runtime_testvars_path = os.path.join(awsy_tests_dir, 'runtime-testvars.json')
114        if kwargs['testvars']:
115            kwargs['testvars'].append(runtime_testvars_path)
116        else:
117            kwargs['testvars'] = [runtime_testvars_path]
118
119        runtime_testvars_file = open(runtime_testvars_path, 'wb')
120        runtime_testvars_file.write(json.dumps(runtime_testvars, indent=2))
121        runtime_testvars_file.close()
122
123        manifest_file = os.path.join(awsy_source_dir,
124                                     'tp5n-pageset.manifest')
125        tooltool_args = {'args': [
126            sys.executable,
127            os.path.join(self.topsrcdir, 'mach'),
128            'artifact', 'toolchain', '-v',
129            '--tooltool-manifest=%s' % manifest_file,
130            '--cache-dir=%s' % os.path.join(self.topsrcdir, 'tooltool-cache'),
131        ]}
132        self.run_process(cwd=page_load_test_dir, **tooltool_args)
133        tp5nzip = os.path.join(page_load_test_dir, 'tp5n.zip')
134        tp5nmanifest = os.path.join(page_load_test_dir, 'tp5n', 'tp5n.manifest')
135        if not os.path.exists(tp5nmanifest):
136            unzip_args = {'args': [
137                'unzip',
138                '-q',
139                '-o',
140                tp5nzip,
141                '-d',
142                page_load_test_dir]}
143            self.run_process(**unzip_args)
144
145        # If '--preferences' was not specified supply our default set.
146        if not kwargs['prefs_files']:
147            kwargs['prefs_files'] = [os.path.join(awsy_source_dir, 'conf', 'prefs.json')]
148
149        # Setup DMD env vars if necessary.
150        if kwargs['dmd']:
151            bin_dir = os.path.dirname(binary)
152
153            if 'DMD' not in os.environ:
154                os.environ['DMD'] = '1'
155
156            # Also add the bin dir to the python path so we can use dmd.py
157            if bin_dir not in sys.path:
158                sys.path.append(bin_dir)
159
160        for k, v in kwargs.iteritems():
161            setattr(args, k, v)
162
163        parser.verify_usage(args)
164
165        args.logger = commandline.setup_logging('Are We Slim Yet Tests',
166                                                args,
167                                                {'mach': sys.stdout})
168        failed = MarionetteHarness(MarionetteTestRunner, args=vars(args)).run()
169        if failed > 0:
170            return 1
171        else:
172            return 0
173
174    @Command('awsy-test', category='testing',
175        description='Run Are We Slim Yet (AWSY) memory usage testing using marionette.',
176        parser=setup_awsy_argument_parser,
177    )
178    @CommandArgumentGroup('AWSY')
179    @CommandArgument('--web-root', group='AWSY', action='store', type=str,
180                     dest='webRootDir',
181                     help='Path to web server root directory. If not specified, '
182                     'defaults to topobjdir/_tests/awsy/html.')
183    @CommandArgument('--page-manifest', group='AWSY', action='store', type=str,
184                     dest='pageManifest',
185                     help='Path to page manifest text file containing a list '
186                     'of urls to test. The urls must be served from localhost. If not '
187                     'specified, defaults to page_load_test/tp5n/tp5n.manifest under '
188                     'the web root.')
189    @CommandArgument('--results', group='AWSY', action='store', type=str,
190                     dest='resultsDir',
191                     help='Path to results directory. If not specified, defaults '
192                     'to the parent directory of the web root.')
193    @CommandArgument('--quick', group='AWSY', action='store_true',
194                     dest='quick', default=False,
195                     help='Set --entities=3, --iterations=1, --per-tab-pause=1, '
196                     '--settle-wait-time=1 for a quick test. Overrides any explicit '
197                     'argument settings.')
198    @CommandArgument('--entities', group='AWSY', action='store', type=int,
199                     dest='entities',
200                     help='Number of urls to load. Defaults to the total number of '
201                     'urls.')
202    @CommandArgument('--max-tabs', group='AWSY', action='store', type=int,
203                     dest='maxTabs',
204                     help='Maximum number of tabs to open. '
205                     'Defaults to %s.' % MAX_TABS)
206    @CommandArgument('--iterations', group='AWSY', action='store', type=int,
207                     dest='iterations',
208                     help='Number of times to run through the test suite. '
209                     'Defaults to %s.' % ITERATIONS)
210    @CommandArgument('--per-tab-pause', group='AWSY', action='store', type=int,
211                     dest='perTabPause',
212                     help='Seconds to wait in between opening tabs. '
213                     'Defaults to %s.' % PER_TAB_PAUSE)
214    @CommandArgument('--settle-wait-time', group='AWSY', action='store', type=int,
215                     dest='settleWaitTime',
216                     help='Seconds to wait for things to settled down. '
217                     'Defaults to %s.' % SETTLE_WAIT_TIME)
218    @CommandArgument('--enable-stylo', group='AWSY', action='store_true',
219                     dest='enable_stylo', default=False,
220                     help='Enable Stylo.')
221    @CommandArgument('--disable-stylo', group='AWSY', action='store_true',
222                     dest='disable_stylo', default=False,
223                     help='Disable Stylo.')
224    @CommandArgument('--single-stylo-traversal', group='AWSY', action='store_true',
225                     dest='single_stylo_traversal', default=False,
226                     help='Set STYLO_THREADS=1.')
227    @CommandArgument('--enable-webrender', group='AWSY', action='store_true',
228                     dest='enable_webrender', default=False,
229                     help='Enable WebRender.')
230    @CommandArgument('--dmd', group='AWSY', action='store_true',
231                     dest='dmd', default=False,
232                     help='Enable DMD during testing. Requires a DMD-enabled build.')
233    def run_awsy_test(self, tests, **kwargs):
234        """mach awsy-test runs the in-tree version of the Are We Slim Yet
235        (AWSY) tests.
236
237        awsy-test is implemented as a marionette test and marionette
238        test arguments also apply although they are not necessary
239        since reasonable defaults will be chosen.
240
241        The AWSY specific arguments can be found in the Command
242        Arguments for AWSY section below.
243
244        awsy-test will automatically download the tp5n.zip talos
245        pageset from tooltool and install it under
246        topobjdir/_tests/awsy/html. You can specify your own page set
247        by specifying --web-root and --page-manifest.
248
249        The results of the test will be placed in the results
250        directory specified by the --results argument.
251
252        On Windows, you may experience problems due to path length
253        errors when extracting the tp5n.zip file containing the
254        test pages or when attempting to write checkpoints to the
255        results directory. In that case, you should specify both
256        the --web-root and --results arguments pointing to a location
257        with a short path. For example:
258
259        --web-root=c:\\\\tmp\\\\html --results=c:\\\\tmp\\\\results
260
261        Note that the double backslashes are required.
262        """
263        kwargs['logger_name'] = 'Awsy Tests'
264        if 'test_objects' in kwargs:
265            tests = []
266            for obj in kwargs['test_objects']:
267                tests.append(obj['file_relpath'])
268            del kwargs['test_objects']
269
270        if not kwargs.get('binary') and conditions.is_firefox(self):
271            kwargs['binary'] = self.get_binary_path('app')
272        return self.run_awsy(tests, **kwargs)
273