How to use patch method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

commitqueuetask_unittest.py

Source: commitqueuetask_unittest.py Github

copy
1# Copyright (c) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29from datetime import datetime
30import unittest
31
32from webkitpy.common.net import bugzilla
33from webkitpy.common.net.layouttestresults import LayoutTestResults
34from webkitpy.common.system.deprecated_logging import error, log
35from webkitpy.common.system.executive import ScriptError
36from webkitpy.common.system.outputcapture import OutputCapture
37from webkitpy.layout_tests.models import test_results
38from webkitpy.layout_tests.models import test_failures
39from webkitpy.thirdparty.mock import Mock
40from webkitpy.tool.bot.commitqueuetask import *
41from webkitpy.tool.bot.expectedfailures import ExpectedFailures
42from webkitpy.tool.mocktool import MockTool
43
44
45class MockCommitQueue(CommitQueueTaskDelegate):
46    def __init__(self, error_plan):
47        self._error_plan = error_plan
48
49    def run_command(self, command):
50        log("run_webkit_patch: %s" % command)
51        if self._error_plan:
52            error = self._error_plan.pop(0)
53            if error:
54                raise error
55
56    def command_passed(self, success_message, patch):
57        log("command_passed: success_message='%s' patch='%s'" % (
58            success_message, patch.id()))
59
60    def command_failed(self, failure_message, script_error, patch):
61        log("command_failed: failure_message='%s' script_error='%s' patch='%s'" % (
62            failure_message, script_error, patch.id()))
63        return 3947
64
65    def refetch_patch(self, patch):
66        return patch
67
68    def expected_failures(self):
69        return ExpectedFailures()
70
71    def layout_test_results(self):
72        return None
73
74    def report_flaky_tests(self, patch, flaky_results, results_archive):
75        flaky_tests = [result.filename for result in flaky_results]
76        log("report_flaky_tests: patch='%s' flaky_tests='%s' archive='%s'" % (patch.id(), flaky_tests, results_archive.filename))
77
78    def archive_last_layout_test_results(self, patch):
79        log("archive_last_layout_test_results: patch='%s'" % patch.id())
80        archive = Mock()
81        archive.filename = "mock-archive-%s.zip" % patch.id()
82        return archive
83
84    def build_style(self):
85        return "both"
86
87
88class FailingTestCommitQueue(MockCommitQueue):
89    def __init__(self, error_plan, test_failure_plan):
90        MockCommitQueue.__init__(self, error_plan)
91        self._test_run_counter = -1  # Special value to indicate tests have never been run.
92        self._test_failure_plan = test_failure_plan
93
94    def run_command(self, command):
95        if command[0] == "build-and-test":
96            self._test_run_counter += 1
97        MockCommitQueue.run_command(self, command)
98
99    def _mock_test_result(self, testname):
100        return test_results.TestResult(testname, [test_failures.FailureTextMismatch()])
101
102    def layout_test_results(self):
103        # Doesn't make sense to ask for the layout_test_results until the tests have run at least once.
104        assert(self._test_run_counter >= 0)
105        failures_for_run = self._test_failure_plan[self._test_run_counter]
106        results = LayoutTestResults(map(self._mock_test_result, failures_for_run))
107        # This makes the results trustable by ExpectedFailures.
108        results.set_failure_limit_count(10)
109        return results
110
111
112# We use GoldenScriptError to make sure that the code under test throws the
113# correct (i.e., golden) exception.
114class GoldenScriptError(ScriptError):
115    pass
116
117
118class CommitQueueTaskTest(unittest.TestCase):
119    def _run_through_task(self, commit_queue, expected_stderr, expected_exception=None, expect_retry=False):
120        tool = MockTool(log_executive=True)
121        patch = tool.bugs.fetch_attachment(10000)
122        task = CommitQueueTask(commit_queue, patch)
123        success = OutputCapture().assert_outputs(self, task.run, expected_stderr=expected_stderr, expected_exception=expected_exception)
124        if not expected_exception:
125            self.assertEqual(success, not expect_retry)
126        return task
127
128    def test_success_case(self):
129        commit_queue = MockCommitQueue([])
130        expected_stderr = """run_webkit_patch: ['clean']
131command_passed: success_message='Cleaned working directory' patch='10000'
132run_webkit_patch: ['update']
133command_passed: success_message='Updated working directory' patch='10000'
134run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
135command_passed: success_message='Applied patch' patch='10000'
136run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
137command_passed: success_message='Built patch' patch='10000'
138run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
139command_passed: success_message='Passed tests' patch='10000'
140run_webkit_patch: ['land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000]
141command_passed: success_message='Landed patch' patch='10000'
142"""
143        self._run_through_task(commit_queue, expected_stderr)
144
145    def test_clean_failure(self):
146        commit_queue = MockCommitQueue([
147            ScriptError("MOCK clean failure"),
148        ])
149        expected_stderr = """run_webkit_patch: ['clean']
150command_failed: failure_message='Unable to clean working directory' script_error='MOCK clean failure' patch='10000'
151"""
152        self._run_through_task(commit_queue, expected_stderr, expect_retry=True)
153
154    def test_update_failure(self):
155        commit_queue = MockCommitQueue([
156            None,
157            ScriptError("MOCK update failure"),
158        ])
159        expected_stderr = """run_webkit_patch: ['clean']
160command_passed: success_message='Cleaned working directory' patch='10000'
161run_webkit_patch: ['update']
162command_failed: failure_message='Unable to update working directory' script_error='MOCK update failure' patch='10000'
163"""
164        self._run_through_task(commit_queue, expected_stderr, expect_retry=True)
165
166    def test_apply_failure(self):
167        commit_queue = MockCommitQueue([
168            None,
169            None,
170            GoldenScriptError("MOCK apply failure"),
171        ])
172        expected_stderr = """run_webkit_patch: ['clean']
173command_passed: success_message='Cleaned working directory' patch='10000'
174run_webkit_patch: ['update']
175command_passed: success_message='Updated working directory' patch='10000'
176run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
177command_failed: failure_message='Patch does not apply' script_error='MOCK apply failure' patch='10000'
178"""
179        self._run_through_task(commit_queue, expected_stderr, GoldenScriptError)
180
181    def test_build_failure(self):
182        commit_queue = MockCommitQueue([
183            None,
184            None,
185            None,
186            GoldenScriptError("MOCK build failure"),
187        ])
188        expected_stderr = """run_webkit_patch: ['clean']
189command_passed: success_message='Cleaned working directory' patch='10000'
190run_webkit_patch: ['update']
191command_passed: success_message='Updated working directory' patch='10000'
192run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
193command_passed: success_message='Applied patch' patch='10000'
194run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
195command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='10000'
196run_webkit_patch: ['build', '--force-clean', '--no-update', '--build-style=both']
197command_passed: success_message='Able to build without patch' patch='10000'
198"""
199        self._run_through_task(commit_queue, expected_stderr, GoldenScriptError)
200
201    def test_red_build_failure(self):
202        commit_queue = MockCommitQueue([
203            None,
204            None,
205            None,
206            ScriptError("MOCK build failure"),
207            ScriptError("MOCK clean build failure"),
208        ])
209        expected_stderr = """run_webkit_patch: ['clean']
210command_passed: success_message='Cleaned working directory' patch='10000'
211run_webkit_patch: ['update']
212command_passed: success_message='Updated working directory' patch='10000'
213run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
214command_passed: success_message='Applied patch' patch='10000'
215run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
216command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='10000'
217run_webkit_patch: ['build', '--force-clean', '--no-update', '--build-style=both']
218command_failed: failure_message='Unable to build without patch' script_error='MOCK clean build failure' patch='10000'
219"""
220        self._run_through_task(commit_queue, expected_stderr, expect_retry=True)
221
222    def test_flaky_test_failure(self):
223        commit_queue = MockCommitQueue([
224            None,
225            None,
226            None,
227            None,
228            ScriptError("MOCK tests failure"),
229        ])
230        # CommitQueueTask will only report flaky tests if we successfully parsed
231        # results.html and returned a LayoutTestResults object, so we fake one.
232        commit_queue.layout_test_results = lambda: LayoutTestResults([])
233        expected_stderr = """run_webkit_patch: ['clean']
234command_passed: success_message='Cleaned working directory' patch='10000'
235run_webkit_patch: ['update']
236command_passed: success_message='Updated working directory' patch='10000'
237run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
238command_passed: success_message='Applied patch' patch='10000'
239run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
240command_passed: success_message='Built patch' patch='10000'
241run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
242command_failed: failure_message='Patch does not pass tests' script_error='MOCK tests failure' patch='10000'
243archive_last_layout_test_results: patch='10000'
244run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
245command_passed: success_message='Passed tests' patch='10000'
246report_flaky_tests: patch='10000' flaky_tests='[]' archive='mock-archive-10000.zip'
247run_webkit_patch: ['land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000]
248command_passed: success_message='Landed patch' patch='10000'
249"""
250        self._run_through_task(commit_queue, expected_stderr)
251
252    def test_failed_archive(self):
253        commit_queue = MockCommitQueue([
254            None,
255            None,
256            None,
257            None,
258            ScriptError("MOCK tests failure"),
259        ])
260        commit_queue.layout_test_results = lambda: LayoutTestResults([])
261        # It's possible delegate to fail to archive layout tests, don't try to report
262        # flaky tests when that happens.
263        commit_queue.archive_last_layout_test_results = lambda patch: None
264        expected_stderr = """run_webkit_patch: ['clean']
265command_passed: success_message='Cleaned working directory' patch='10000'
266run_webkit_patch: ['update']
267command_passed: success_message='Updated working directory' patch='10000'
268run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
269command_passed: success_message='Applied patch' patch='10000'
270run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
271command_passed: success_message='Built patch' patch='10000'
272run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
273command_failed: failure_message='Patch does not pass tests' script_error='MOCK tests failure' patch='10000'
274run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
275command_passed: success_message='Passed tests' patch='10000'
276run_webkit_patch: ['land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000]
277command_passed: success_message='Landed patch' patch='10000'
278"""
279        self._run_through_task(commit_queue, expected_stderr)
280
281    def test_double_flaky_test_failure(self):
282        commit_queue = FailingTestCommitQueue([
283            None,
284            None,
285            None,
286            None,
287            ScriptError("MOCK test failure"),
288            ScriptError("MOCK test failure again"),
289        ], [
290            "foo.html",
291            "bar.html",
292            "foo.html",
293        ])
294        # The (subtle) point of this test is that report_flaky_tests does not appear
295        # in the expected_stderr for this run.
296        # Note also that there is no attempt to run the tests w/o the patch.
297        expected_stderr = """run_webkit_patch: ['clean']
298command_passed: success_message='Cleaned working directory' patch='10000'
299run_webkit_patch: ['update']
300command_passed: success_message='Updated working directory' patch='10000'
301run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
302command_passed: success_message='Applied patch' patch='10000'
303run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
304command_passed: success_message='Built patch' patch='10000'
305run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
306command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='10000'
307archive_last_layout_test_results: patch='10000'
308run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
309command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='10000'
310"""
311        tool = MockTool(log_executive=True)
312        patch = tool.bugs.fetch_attachment(10000)
313        task = CommitQueueTask(commit_queue, patch)
314        success = OutputCapture().assert_outputs(self, task.run, expected_stderr=expected_stderr)
315        self.assertEqual(success, False)
316
317    def test_test_failure(self):
318        commit_queue = MockCommitQueue([
319            None,
320            None,
321            None,
322            None,
323            GoldenScriptError("MOCK test failure"),
324            ScriptError("MOCK test failure again"),
325        ])
326        expected_stderr = """run_webkit_patch: ['clean']
327command_passed: success_message='Cleaned working directory' patch='10000'
328run_webkit_patch: ['update']
329command_passed: success_message='Updated working directory' patch='10000'
330run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
331command_passed: success_message='Applied patch' patch='10000'
332run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
333command_passed: success_message='Built patch' patch='10000'
334run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
335command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='10000'
336archive_last_layout_test_results: patch='10000'
337run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
338command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='10000'
339archive_last_layout_test_results: patch='10000'
340run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive']
341command_passed: success_message='Able to pass tests without patch' patch='10000'
342"""
343        self._run_through_task(commit_queue, expected_stderr, GoldenScriptError)
344
345    def test_red_test_failure(self):
346        commit_queue = FailingTestCommitQueue([
347            None,
348            None,
349            None,
350            None,
351            ScriptError("MOCK test failure"),
352            ScriptError("MOCK test failure again"),
353            ScriptError("MOCK clean test failure"),
354        ], [
355            "foo.html",
356            "foo.html",
357            "foo.html",
358        ])
359
360        # Tests always fail, and always return the same results, but we
361        # should still be able to land in this case!
362        expected_stderr = """run_webkit_patch: ['clean']
363command_passed: success_message='Cleaned working directory' patch='10000'
364run_webkit_patch: ['update']
365command_passed: success_message='Updated working directory' patch='10000'
366run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
367command_passed: success_message='Applied patch' patch='10000'
368run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
369command_passed: success_message='Built patch' patch='10000'
370run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
371command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='10000'
372archive_last_layout_test_results: patch='10000'
373run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
374command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='10000'
375archive_last_layout_test_results: patch='10000'
376run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive']
377command_failed: failure_message='Unable to pass tests without patch (tree is red?)' script_error='MOCK clean test failure' patch='10000'
378run_webkit_patch: ['land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000]
379command_passed: success_message='Landed patch' patch='10000'
380"""
381        self._run_through_task(commit_queue, expected_stderr)
382
383    def test_very_red_tree_retry(self):
384        lots_of_failing_tests = map(lambda num: "test-%s.html" % num, range(0, 100))
385        commit_queue = FailingTestCommitQueue([
386            None,
387            None,
388            None,
389            None,
390            ScriptError("MOCK test failure"),
391            ScriptError("MOCK test failure again"),
392            ScriptError("MOCK clean test failure"),
393        ], [
394            lots_of_failing_tests,
395            lots_of_failing_tests,
396            lots_of_failing_tests,
397        ])
398
399        # Tests always fail, and return so many failures that we do not
400        # trust the results (see ExpectedFailures._can_trust_results) so we
401        # just give up and retry the patch.
402        expected_stderr = """run_webkit_patch: ['clean']
403command_passed: success_message='Cleaned working directory' patch='10000'
404run_webkit_patch: ['update']
405command_passed: success_message='Updated working directory' patch='10000'
406run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
407command_passed: success_message='Applied patch' patch='10000'
408run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
409command_passed: success_message='Built patch' patch='10000'
410run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
411command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='10000'
412archive_last_layout_test_results: patch='10000'
413run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
414command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='10000'
415archive_last_layout_test_results: patch='10000'
416run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive']
417command_failed: failure_message='Unable to pass tests without patch (tree is red?)' script_error='MOCK clean test failure' patch='10000'
418"""
419        self._run_through_task(commit_queue, expected_stderr, expect_retry=True)
420
421    def test_red_tree_patch_rejection(self):
422        commit_queue = FailingTestCommitQueue([
423            None,
424            None,
425            None,
426            None,
427            GoldenScriptError("MOCK test failure"),
428            ScriptError("MOCK test failure again"),
429            ScriptError("MOCK clean test failure"),
430        ], [
431            ["foo.html", "bar.html"],
432            ["foo.html", "bar.html"],
433            ["foo.html"],
434        ])
435
436        # Tests always fail, but the clean tree only fails one test
437        # while the patch fails two.  So we should reject the patch!
438        expected_stderr = """run_webkit_patch: ['clean']
439command_passed: success_message='Cleaned working directory' patch='10000'
440run_webkit_patch: ['update']
441command_passed: success_message='Updated working directory' patch='10000'
442run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
443command_passed: success_message='Applied patch' patch='10000'
444run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
445command_passed: success_message='Built patch' patch='10000'
446run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
447command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='10000'
448archive_last_layout_test_results: patch='10000'
449run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
450command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='10000'
451archive_last_layout_test_results: patch='10000'
452run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive']
453command_failed: failure_message='Unable to pass tests without patch (tree is red?)' script_error='MOCK clean test failure' patch='10000'
454"""
455        task = self._run_through_task(commit_queue, expected_stderr, GoldenScriptError)
456        self.assertEqual(task.results_from_patch_test_run(task._patch).failing_tests(), ["foo.html", "bar.html"])
457
458    def test_land_failure(self):
459        commit_queue = MockCommitQueue([
460            None,
461            None,
462            None,
463            None,
464            None,
465            GoldenScriptError("MOCK land failure"),
466        ])
467        expected_stderr = """run_webkit_patch: ['clean']
468command_passed: success_message='Cleaned working directory' patch='10000'
469run_webkit_patch: ['update']
470command_passed: success_message='Updated working directory' patch='10000'
471run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
472command_passed: success_message='Applied patch' patch='10000'
473run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
474command_passed: success_message='Built patch' patch='10000'
475run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive']
476command_passed: success_message='Passed tests' patch='10000'
477run_webkit_patch: ['land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000]
478command_failed: failure_message='Unable to land patch' script_error='MOCK land failure' patch='10000'
479"""
480        # FIXME: This should really be expect_retry=True for a better user experiance.
481        self._run_through_task(commit_queue, expected_stderr, GoldenScriptError)
482
483    def _expect_validate(self, patch, is_valid):
484        class MockDelegate(object):
485            def refetch_patch(self, patch):
486                return patch
487
488            def expected_failures(self):
489                return ExpectedFailures()
490
491        task = CommitQueueTask(MockDelegate(), patch)
492        self.assertEquals(task.validate(), is_valid)
493
494    def _mock_patch(self, attachment_dict={}, bug_dict={'bug_status': 'NEW'}, committer="fake"):
495        bug = bugzilla.Bug(bug_dict, None)
496        patch = bugzilla.Attachment(attachment_dict, bug)
497        patch._committer = committer
498        return patch
499
500    def test_validate(self):
501        self._expect_validate(self._mock_patch(), True)
502        self._expect_validate(self._mock_patch({'is_obsolete': True}), False)
503        self._expect_validate(self._mock_patch(bug_dict={'bug_status': 'CLOSED'}), False)
504        self._expect_validate(self._mock_patch(committer=None), False)
505        self._expect_validate(self._mock_patch({'review': '-'}), False)
506
Full Screen

patch_test.py

Source: patch_test.py Github

copy
1#!/usr/bin/env python
2# coding: utf-8
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit tests for patch.py."""
8
9import logging
10import os
11import posixpath
12import sys
13import unittest
14
15sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16
17from testing_support.patches_data import GIT, RAW
18
19import patch
20
21
22class PatchTest(unittest.TestCase):
23  def _check_patch(self,
24      p,
25      filename,
26      diff,
27      source_filename=None,
28      is_binary=False,
29      is_delete=False,
30      is_git_diff=False,
31      is_new=False,
32      patchlevel=0,
33      svn_properties=None,
34      nb_hunks=None):
35    self.assertEquals(p.filename, filename)
36    self.assertEquals(p.source_filename, source_filename)
37    self.assertEquals(p.is_binary, is_binary)
38    self.assertEquals(p.is_delete, is_delete)
39    if hasattr(p, 'is_git_diff'):
40      self.assertEquals(p.is_git_diff, is_git_diff)
41    self.assertEquals(p.is_new, is_new)
42    if hasattr(p, 'patchlevel'):
43      self.assertEquals(p.patchlevel, patchlevel)
44    if diff:
45      if is_binary:
46        self.assertEquals(p.get(), diff)
47      else:
48        self.assertEquals(p.get(True), diff)
49    if hasattr(p, 'hunks'):
50      self.assertEquals(len(p.hunks), nb_hunks)
51    else:
52      self.assertEquals(None, nb_hunks)
53    if hasattr(p, 'svn_properties'):
54      self.assertEquals(p.svn_properties, svn_properties or [])
55
56  def testFilePatchDelete(self):
57    p = patch.FilePatchDelete('foo', False)
58    self._check_patch(p, 'foo', None, is_delete=True)
59
60  def testFilePatchDeleteBin(self):
61    p = patch.FilePatchDelete('foo', True)
62    self._check_patch(p, 'foo', None, is_delete=True, is_binary=True)
63
64  def testFilePatchBinary(self):
65    p = patch.FilePatchBinary('foo', 'data', [], is_new=False)
66    self._check_patch(p, 'foo', 'data', is_binary=True)
67
68  def testFilePatchBinaryNew(self):
69    p = patch.FilePatchBinary('foo', 'data', [], is_new=True)
70    self._check_patch(p, 'foo', 'data', is_binary=True, is_new=True)
71
72  def testFilePatchDiff(self):
73    p = patch.FilePatchDiff('chrome/file.cc', RAW.PATCH, [])
74    self._check_patch(p, 'chrome/file.cc', RAW.PATCH, nb_hunks=1)
75
76  def testDifferent(self):
77    name = 'master/unittests/data/processes-summary.dat'
78    p = patch.FilePatchDiff(name, RAW.DIFFERENT, [])
79    self._check_patch(p, name, RAW.DIFFERENT, nb_hunks=1)
80
81  def testFilePatchDiffHeaderMode(self):
82    p = patch.FilePatchDiff('git_cl/git-cl', GIT.MODE_EXE, [])
83    self._check_patch(
84        p, 'git_cl/git-cl', GIT.MODE_EXE, is_git_diff=True, patchlevel=1,
85        svn_properties=[('svn:executable', '.')], nb_hunks=0)
86
87  def testFilePatchDiffHeaderModeIndex(self):
88    p = patch.FilePatchDiff('git_cl/git-cl', GIT.MODE_EXE_JUNK, [])
89    self._check_patch(
90        p, 'git_cl/git-cl', GIT.MODE_EXE_JUNK, is_git_diff=True, patchlevel=1,
91        svn_properties=[('svn:executable', '.')], nb_hunks=0)
92
93  def testFilePatchDiffHeaderNotExecutable(self):
94    p = patch.FilePatchDiff(
95        'build/android/ant/create.js', GIT.NEW_NOT_EXECUTABLE, [])
96    self._check_patch(
97        p, 'build/android/ant/create.js', GIT.NEW_NOT_EXECUTABLE,
98        is_git_diff=True, patchlevel=1, is_new=True,
99        nb_hunks=1)
100
101  def testFilePatchDiffSvnNew(self):
102    # The code path is different for git and svn.
103    p = patch.FilePatchDiff('foo', RAW.NEW, [])
104    self._check_patch(p, 'foo', RAW.NEW, is_new=True, nb_hunks=1)
105
106  def testFilePatchDiffGitNew(self):
107    # The code path is different for git and svn.
108    p = patch.FilePatchDiff('foo', GIT.NEW, [])
109    self._check_patch(
110        p, 'foo', GIT.NEW, is_new=True, is_git_diff=True, patchlevel=1,
111        nb_hunks=1)
112
113  def testSvn(self):
114    # Should not throw.
115    p = patch.FilePatchDiff('chrome/file.cc', RAW.PATCH, [])
116    lines = RAW.PATCH.splitlines(True)
117    header = ''.join(lines[:4])
118    hunks = ''.join(lines[4:])
119    self.assertEquals(header, p.diff_header)
120    self.assertEquals(hunks, p.diff_hunks)
121    self.assertEquals(RAW.PATCH, p.get(True))
122    self.assertEquals(RAW.PATCH, p.get(False))
123
124  def testSvnNew(self):
125    p = patch.FilePatchDiff('chrome/file.cc', RAW.MINIMAL_NEW, [])
126    self.assertEquals(RAW.MINIMAL_NEW, p.diff_header)
127    self.assertEquals('', p.diff_hunks)
128    self.assertEquals(RAW.MINIMAL_NEW, p.get(True))
129    self.assertEquals(RAW.MINIMAL_NEW, p.get(False))
130
131  def testSvnDelete(self):
132    p = patch.FilePatchDiff('chrome/file.cc', RAW.MINIMAL_DELETE, [])
133    self.assertEquals(RAW.MINIMAL_DELETE, p.diff_header)
134    self.assertEquals('', p.diff_hunks)
135    self.assertEquals(RAW.MINIMAL_DELETE, p.get(True))
136    self.assertEquals(RAW.MINIMAL_DELETE, p.get(False))
137
138  def testSvnRename(self):
139    p = patch.FilePatchDiff('file_b', RAW.MINIMAL_RENAME, [])
140    self.assertEquals(RAW.MINIMAL_RENAME, p.diff_header)
141    self.assertEquals('', p.diff_hunks)
142    self.assertEquals(RAW.MINIMAL_RENAME, p.get(True))
143    self.assertEquals('--- file_b\n+++ file_b\n', p.get(False))
144
145  def testRelPath(self):
146    patches = patch.PatchSet([
147        patch.FilePatchDiff('pp', GIT.COPY, []),
148        patch.FilePatchDiff(
149            'chromeos\\views/webui_menu_widget.h', GIT.RENAME_PARTIAL, []),
150        patch.FilePatchDiff('tools/run_local_server.sh', GIT.RENAME, []),
151        patch.FilePatchBinary('bar', 'data', [], is_new=False),
152        patch.FilePatchDiff('chrome/file.cc', RAW.PATCH, []),
153        patch.FilePatchDiff('foo', GIT.NEW, []),
154        patch.FilePatchDelete('other/place/foo', True),
155        patch.FilePatchDiff(
156            'tools\\clang_check/README.chromium', GIT.DELETE, []),
157    ])
158    expected = [
159        'pp',
160        'chromeos/views/webui_menu_widget.h',
161        'tools/run_local_server.sh',
162        'bar',
163        'chrome/file.cc',
164        'foo',
165        'other/place/foo',
166        'tools/clang_check/README.chromium',
167    ]
168    self.assertEquals(expected, patches.filenames)
169
170    # Test patch #4.
171    orig_name = patches.patches[4].filename
172    orig_source_name = patches.patches[4].source_filename or orig_name
173    patches.set_relpath(os.path.join('a', 'bb'))
174    # Expect posixpath all the time.
175    expected = [posixpath.join('a', 'bb', x) for x in expected]
176    self.assertEquals(expected, patches.filenames)
177    # Make sure each header is updated accordingly.
178    header = []
179    new_name = posixpath.join('a', 'bb', orig_name)
180    new_source_name = posixpath.join('a', 'bb', orig_source_name)
181    for line in RAW.PATCH.splitlines(True):
182      if line.startswith('@@'):
183        break
184      if line[:3] == '---':
185        line = line.replace(orig_source_name, new_source_name)
186      if line[:3] == '+++':
187        line = line.replace(orig_name, new_name)
188      header.append(line)
189    header = ''.join(header)
190    self.assertEquals(header, patches.patches[4].diff_header)
191
192  def testRelPathEmpty(self):
193    patches = patch.PatchSet([
194        patch.FilePatchDiff('chrome\\file.cc', RAW.PATCH, []),
195        patch.FilePatchDelete('other\\place\\foo', True),
196    ])
197    patches.set_relpath('')
198    self.assertEquals(
199        ['chrome/file.cc', 'other/place/foo'],
200        [f.filename for f in patches])
201    self.assertEquals([None, None], [f.source_filename for f in patches])
202
203  def testBackSlash(self):
204    mangled_patch = RAW.PATCH.replace('chrome/', 'chrome\\')
205    patches = patch.PatchSet([
206        patch.FilePatchDiff('chrome\\file.cc', mangled_patch, []),
207        patch.FilePatchDelete('other\\place\\foo', True),
208    ])
209    expected = ['chrome/file.cc', 'other/place/foo']
210    self.assertEquals(expected, patches.filenames)
211    self.assertEquals(RAW.PATCH, patches.patches[0].get(True))
212    self.assertEquals(RAW.PATCH, patches.patches[0].get(False))
213
214  def testTwoHunks(self):
215    name = 'chrome/app/generated_resources.grd'
216    p = patch.FilePatchDiff(name, RAW.TWO_HUNKS, [])
217    self._check_patch(p, name, RAW.TWO_HUNKS, nb_hunks=2)
218
219  def testGitThreeHunks(self):
220    p = patch.FilePatchDiff('presubmit_support.py', GIT.FOUR_HUNKS, [])
221    self._check_patch(
222        p, 'presubmit_support.py', GIT.FOUR_HUNKS, is_git_diff=True,
223        patchlevel=1,
224        nb_hunks=4)
225
226  def testDelete(self):
227    p = patch.FilePatchDiff('tools/clang_check/README.chromium', RAW.DELETE, [])
228    self._check_patch(
229        p, 'tools/clang_check/README.chromium', RAW.DELETE, is_delete=True,
230        nb_hunks=1)
231
232  def testDelete2(self):
233    name = 'browser/extensions/extension_sidebar_api.cc'
234    p = patch.FilePatchDiff(name, RAW.DELETE2, [])
235    self._check_patch(p, name, RAW.DELETE2, is_delete=True, nb_hunks=1)
236
237  def testGitDelete(self):
238    p = patch.FilePatchDiff('tools/clang_check/README.chromium', GIT.DELETE, [])
239    self._check_patch(
240        p, 'tools/clang_check/README.chromium', GIT.DELETE, is_delete=True,
241        is_git_diff=True, patchlevel=1, nb_hunks=1)
242
243  def testGitRename(self):
244    p = patch.FilePatchDiff('tools/run_local_server.sh', GIT.RENAME, [])
245    self._check_patch(
246        p,
247        'tools/run_local_server.sh',
248        GIT.RENAME,
249        is_git_diff=True,
250        patchlevel=1,
251        source_filename='tools/run_local_server.PY',
252        is_new=True,
253        nb_hunks=0)
254
255  def testGitRenamePartial(self):
256    p = patch.FilePatchDiff(
257        'chromeos/views/webui_menu_widget.h', GIT.RENAME_PARTIAL, [])
258    self._check_patch(
259        p,
260        'chromeos/views/webui_menu_widget.h',
261        GIT.RENAME_PARTIAL,
262        source_filename='chromeos/views/DOMui_menu_widget.h',
263        is_git_diff=True,
264        patchlevel=1,
265        is_new=True,
266        nb_hunks=1)
267
268  def testGitCopy(self):
269    p = patch.FilePatchDiff('pp', GIT.COPY, [])
270    self._check_patch(
271        p, 'pp', GIT.COPY, is_git_diff=True, patchlevel=1,
272        source_filename='PRESUBMIT.py', is_new=True, nb_hunks=0)
273
274  def testOnlyHeader(self):
275    p = patch.FilePatchDiff('file_a', RAW.MINIMAL, [])
276    self._check_patch(p, 'file_a', RAW.MINIMAL, nb_hunks=0)
277
278  def testSmallest(self):
279    p = patch.FilePatchDiff('file_a', RAW.NEW_NOT_NULL, [])
280    self._check_patch(p, 'file_a', RAW.NEW_NOT_NULL, is_new=True, nb_hunks=1)
281
282  def testRenameOnlyHeader(self):
283    p = patch.FilePatchDiff('file_b', RAW.MINIMAL_RENAME, [])
284    self._check_patch(
285        p, 'file_b', RAW.MINIMAL_RENAME, source_filename='file_a', is_new=True,
286        nb_hunks=0)
287
288  def testUnicodeFilenameGet(self):
289    p = patch.FilePatchDiff(u'filé_b', RAW.RENAME_UTF8, [])
290    self._check_patch(
291        p, u'filé_b', RAW.RENAME_UTF8, source_filename=u'file_à', is_new=True,
292        nb_hunks=1)
293    self.assertTrue(isinstance(p.get(False), str))
294    p.set_relpath('foo')
295    self.assertTrue(isinstance(p.get(False), str))
296    self.assertEquals(u'foo/file_à'.encode('utf-8'), p.source_filename_utf8)
297    self.assertEquals(u'foo/file_à', p.source_filename)
298    self.assertEquals(u'foo/filé_b'.encode('utf-8'), p.filename_utf8)
299    self.assertEquals(u'foo/filé_b', p.filename)
300
301  def testGitCopyPartial(self):
302    p = patch.FilePatchDiff('wtf2', GIT.COPY_PARTIAL, [])
303    self._check_patch(
304        p, 'wtf2', GIT.COPY_PARTIAL, source_filename='wtf', is_git_diff=True,
305        patchlevel=1, is_new=True, nb_hunks=1)
306
307  def testGitCopyPartialAsSvn(self):
308    p = patch.FilePatchDiff('wtf2', GIT.COPY_PARTIAL, [])
309    # TODO(maruel): Improve processing.
310    diff = (
311        'diff --git a/wtf2 b/wtf22\n'
312        'similarity index 98%\n'
313        'copy from wtf2\n'
314        'copy to wtf22\n'
315        'index 79fbaf3..3560689 100755\n'
316        '--- a/wtf2\n'
317        '+++ b/wtf22\n'
318        '@@ -1,4 +1,4 @@\n'
319        '-#!/usr/bin/env python\n'
320        '+#!/usr/bin/env python1.3\n'
321        ' # Copyright (c) 2010 The Chromium Authors. All rights reserved.\n'
322        ' # blah blah blah as\n'
323        ' # found in the LICENSE file.\n')
324    self.assertEquals(diff, p.get(False))
325
326  def testGitNewExe(self):
327    p = patch.FilePatchDiff('natsort_test.py', GIT.NEW_EXE, [])
328    self._check_patch(
329        p,
330        'natsort_test.py',
331        GIT.NEW_EXE,
332        is_new=True,
333        is_git_diff=True,
334        patchlevel=1,
335        svn_properties=[('svn:executable', '.')],
336        nb_hunks=1)
337
338  def testGitNewMode(self):
339    p = patch.FilePatchDiff('natsort_test.py', GIT.NEW_MODE, [])
340    self._check_patch(
341        p, 'natsort_test.py', GIT.NEW_MODE, is_new=True, is_git_diff=True,
342        patchlevel=1, nb_hunks=1)
343
344  def testPatchsetOrder(self):
345    # Deletes must be last.
346    # File renames/move/copy must be first.
347    patches = [
348        patch.FilePatchDiff('chrome/file.cc', RAW.PATCH, []),
349        patch.FilePatchDiff(
350            'tools\\clang_check/README.chromium', GIT.DELETE, []),
351        patch.FilePatchDiff('tools/run_local_server.sh', GIT.RENAME, []),
352        patch.FilePatchDiff(
353            'chromeos\\views/webui_menu_widget.h', GIT.RENAME_PARTIAL, []),
354        patch.FilePatchDiff('pp', GIT.COPY, []),
355        patch.FilePatchDiff('foo', GIT.NEW, []),
356        patch.FilePatchDelete('other/place/foo', True),
357        patch.FilePatchBinary('bar', 'data', [], is_new=False),
358    ]
359    expected = [
360        'pp',
361        'chromeos/views/webui_menu_widget.h',
362        'tools/run_local_server.sh',
363        'bar',
364        'chrome/file.cc',
365        'foo',
366        'other/place/foo',
367        'tools/clang_check/README.chromium',
368    ]
369    patchset = patch.PatchSet(patches)
370    self.assertEquals(expected, patchset.filenames)
371
372  def testGitPatch(self):
373    p = patch.FilePatchDiff('chrome/file.cc', GIT.PATCH, [])
374    self._check_patch(
375        p, 'chrome/file.cc', GIT.PATCH, is_git_diff=True, patchlevel=1,
376        nb_hunks=1)
377
378  def testGitPatchShortHunkHeader(self):
379    p = patch.FilePatchDiff(
380        'chrome/browser/api/OWNERS', GIT.PATCH_SHORT_HUNK_HEADER, [])
381    self._check_patch(
382        p, 'chrome/browser/api/OWNERS', GIT.PATCH_SHORT_HUNK_HEADER,
383        is_git_diff=True, patchlevel=1, nb_hunks=1)
384
385
386class PatchTestFail(unittest.TestCase):
387  # All patches that should throw.
388  def testFilePatchDelete(self):
389    self.assertFalse(hasattr(patch.FilePatchDelete('foo', False), 'get'))
390
391  def testFilePatchDeleteBin(self):
392    self.assertFalse(hasattr(patch.FilePatchDelete('foo', True), 'get'))
393
394  def testFilePatchDiffBad(self):
395    try:
396      patch.FilePatchDiff('foo', 'data', [])
397      self.fail()
398    except patch.UnsupportedPatchFormat:
399      pass
400
401  def testFilePatchDiffEmpty(self):
402    try:
403      patch.FilePatchDiff('foo', '', [])
404      self.fail()
405    except patch.UnsupportedPatchFormat:
406      pass
407
408  def testFilePatchDiffNone(self):
409    try:
410      patch.FilePatchDiff('foo', None, [])
411      self.fail()
412    except patch.UnsupportedPatchFormat:
413      pass
414
415  def testFilePatchBadDiffName(self):
416    try:
417      patch.FilePatchDiff('foo', RAW.PATCH, [])
418      self.fail()
419    except patch.UnsupportedPatchFormat, e:
420      self.assertEquals(
421          "Can't process patch for file foo.\nUnexpected diff: chrome/file.cc.",
422          str(e))
423
424  def testFilePatchDiffBadHeader(self):
425    try:
426      diff = (
427        '+++ b/foo\n'
428        '@@ -0,0 +1 @@\n'
429        '+bar\n')
430      patch.FilePatchDiff('foo', diff, [])
431      self.fail()
432    except patch.UnsupportedPatchFormat:
433      pass
434
435  def testFilePatchDiffBadGitHeader(self):
436    try:
437      diff = (
438        'diff --git a/foo b/foo\n'
439        '+++ b/foo\n'
440        '@@ -0,0 +1 @@\n'
441        '+bar\n')
442      patch.FilePatchDiff('foo', diff, [])
443      self.fail()
444    except patch.UnsupportedPatchFormat:
445      pass
446
447  def testFilePatchDiffBadHeaderReversed(self):
448    try:
449      diff = (
450        '+++ b/foo\n'
451        '--- b/foo\n'
452        '@@ -0,0 +1 @@\n'
453        '+bar\n')
454      patch.FilePatchDiff('foo', diff, [])
455      self.fail()
456    except patch.UnsupportedPatchFormat:
457      pass
458
459  def testFilePatchDiffGitBadHeaderReversed(self):
460    try:
461      diff = (
462        'diff --git a/foo b/foo\n'
463        '+++ b/foo\n'
464        '--- b/foo\n'
465        '@@ -0,0 +1 @@\n'
466        '+bar\n')
467      patch.FilePatchDiff('foo', diff, [])
468      self.fail()
469    except patch.UnsupportedPatchFormat:
470      pass
471
472  def testFilePatchDiffInvalidGit(self):
473    try:
474      patch.FilePatchDiff('svn_utils_test.txt', (
475        'diff --git a/tests/svn_utils_test_data/svn_utils_test.txt '
476        'b/tests/svn_utils_test_data/svn_utils_test.txt\n'
477        'index 0e4de76..8320059 100644\n'
478        '--- a/svn_utils_test.txt\n'
479        '+++ b/svn_utils_test.txt\n'
480        '@@ -3,6 +3,7 @@ bb\n'
481        'ccc\n'
482        'dd\n'
483        'e\n'
484        '+FOO!\n'
485        'ff\n'
486        'ggg\n'
487        'hh\n'),
488        [])
489      self.fail()
490    except patch.UnsupportedPatchFormat:
491      pass
492    try:
493      patch.FilePatchDiff('svn_utils_test2.txt', (
494        'diff --git a/svn_utils_test_data/svn_utils_test.txt '
495        'b/svn_utils_test.txt\n'
496        'index 0e4de76..8320059 100644\n'
497        '--- a/svn_utils_test.txt\n'
498        '+++ b/svn_utils_test.txt\n'
499        '@@ -3,6 +3,7 @@ bb\n'
500        'ccc\n'
501        'dd\n'
502        'e\n'
503        '+FOO!\n'
504        'ff\n'
505        'ggg\n'
506        'hh\n'),
507        [])
508      self.fail()
509    except patch.UnsupportedPatchFormat:
510      pass
511
512  def testRelPathBad(self):
513    patches = patch.PatchSet([
514        patch.FilePatchDiff('chrome\\file.cc', RAW.PATCH, []),
515        patch.FilePatchDelete('other\\place\\foo', True),
516    ])
517    try:
518      patches.set_relpath('..')
519      self.fail()
520    except patch.UnsupportedPatchFormat:
521      pass
522
523  def testInverted(self):
524    try:
525      patch.FilePatchDiff(
526        'file_a', '+++ file_a\n--- file_a\[email protected]@ -0,0 +1 @@\n+foo\n', [])
527      self.fail()
528    except patch.UnsupportedPatchFormat:
529      pass
530
531  def testInvertedOnlyHeader(self):
532    try:
533      patch.FilePatchDiff('file_a', '+++ file_a\n--- file_a\n', [])
534      self.fail()
535    except patch.UnsupportedPatchFormat:
536      pass
537
538  def testBadHunkCommas(self):
539    try:
540      patch.FilePatchDiff(
541        'file_a',
542        '--- file_a\n'
543        '+++ file_a\n'
544        '@@ -0,,0 +1 @@\n'
545        '+foo\n',
546        [])
547      self.fail()
548    except patch.UnsupportedPatchFormat:
549      pass
550
551
552if __name__ == '__main__':
553  logging.basicConfig(level=
554      [logging.WARNING, logging.INFO, logging.DEBUG][
555        min(2, sys.argv.count('-v'))])
556  unittest.main()
557
Full Screen

queues.py

Source: queues.py Github

copy
1# Copyright (c) 2009 Google Inc. All rights reserved.
2# Copyright (c) 2009 Apple Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7# 
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17# 
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30from __future__ import with_statement
31
32import codecs
33import time
34import traceback
35import os
36
37from datetime import datetime
38from optparse import make_option
39from StringIO import StringIO
40
41from webkitpy.common.config.committervalidator import CommitterValidator
42from webkitpy.common.net.bugzilla import Attachment
43from webkitpy.common.net.statusserver import StatusServer
44from webkitpy.common.system.deprecated_logging import error, log
45from webkitpy.common.system.executive import ScriptError
46from webkitpy.tool.bot.botinfo import BotInfo
47from webkitpy.tool.bot.commitqueuetask import CommitQueueTask, CommitQueueTaskDelegate
48from webkitpy.tool.bot.expectedfailures import ExpectedFailures
49from webkitpy.tool.bot.feeders import CommitQueueFeeder, EWSFeeder
50from webkitpy.tool.bot.layouttestresultsreader import LayoutTestResultsReader
51from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate
52from webkitpy.tool.bot.flakytestreporter import FlakyTestReporter
53from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler
54from webkitpy.tool.multicommandtool import Command, TryAgain
55
56
57class AbstractQueue(Command, QueueEngineDelegate):
58    watchers = [
59    ]
60
61    _pass_status = "Pass"
62    _fail_status = "Fail"
63    _retry_status = "Retry"
64    _error_status = "Error"
65
66    def __init__(self, options=None): # Default values should never be collections (like []) as default values are shared between invocations
67        options_list = (options or []) + [
68            make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue.  Dangerous!"),
69            make_option("--exit-after-iteration", action="store", type="int", dest="iterations", default=None, help="Stop running the queue after iterating this number of times."),
70        ]
71        Command.__init__(self, "Run the %s" % self.name, options=options_list)
72        self._iteration_count = 0
73
74    def _cc_watchers(self, bug_id):
75        try:
76            self._tool.bugs.add_cc_to_bug(bug_id, self.watchers)
77        except Exception, e:
78            traceback.print_exc()
79            log("Failed to CC watchers.")
80
81    def run_webkit_patch(self, args):
82        webkit_patch_args = [self._tool.path()]
83        # FIXME: This is a hack, we should have a more general way to pass global options.
84        # FIXME: We must always pass global options and their value in one argument
85        # because our global option code looks for the first argument which does
86        # not begin with "-" and assumes that is the command name.
87        webkit_patch_args += ["--status-host=%s" % self._tool.status_server.host]
88        if self._tool.status_server.bot_id:
89            webkit_patch_args += ["--bot-id=%s" % self._tool.status_server.bot_id]
90        if self._options.port:
91            webkit_patch_args += ["--port=%s" % self._options.port]
92        webkit_patch_args.extend(args)
93        # FIXME: There is probably no reason to use run_and_throw_if_fail anymore.
94        # run_and_throw_if_fail was invented to support tee'd output
95        # (where we write both to a log file and to the console at once),
96        # but the queues don't need live-progress, a dump-of-output at the
97        # end should be sufficient.
98        return self._tool.executive.run_and_throw_if_fail(webkit_patch_args, cwd=self._tool.scm().checkout_root)
99
100    def _log_directory(self):
101        return os.path.join("..", "%s-logs" % self.name)
102
103    # QueueEngineDelegate methods
104
105    def queue_log_path(self):
106        return os.path.join(self._log_directory(), "%s.log" % self.name)
107
108    def work_item_log_path(self, work_item):
109        raise NotImplementedError, "subclasses must implement"
110
111    def begin_work_queue(self):
112        log("CAUTION: %s will discard all local changes in \"%s\"" % (self.name, self._tool.scm().checkout_root))
113        if self._options.confirm:
114            response = self._tool.user.prompt("Are you sure?  Type \"yes\" to continue: ")
115            if (response != "yes"):
116                error("User declined.")
117        log("Running WebKit %s." % self.name)
118        self._tool.status_server.update_status(self.name, "Starting Queue")
119
120    def stop_work_queue(self, reason):
121        self._tool.status_server.update_status(self.name, "Stopping Queue, reason: %s" % reason)
122
123    def should_continue_work_queue(self):
124        self._iteration_count += 1
125        return not self._options.iterations or self._iteration_count <= self._options.iterations
126
127    def next_work_item(self):
128        raise NotImplementedError, "subclasses must implement"
129
130    def should_proceed_with_work_item(self, work_item):
131        raise NotImplementedError, "subclasses must implement"
132
133    def process_work_item(self, work_item):
134        raise NotImplementedError, "subclasses must implement"
135
136    def handle_unexpected_error(self, work_item, message):
137        raise NotImplementedError, "subclasses must implement"
138
139    # Command methods
140
141    def execute(self, options, args, tool, engine=QueueEngine):
142        self._options = options # FIXME: This code is wrong.  Command.options is a list, this assumes an Options element!
143        self._tool = tool  # FIXME: This code is wrong too!  Command.bind_to_tool handles this!
144        return engine(self.name, self, self._tool.wakeup_event).run()
145
146    @classmethod
147    def _log_from_script_error_for_upload(cls, script_error, output_limit=None):
148        # We have seen request timeouts with app engine due to large
149        # log uploads.  Trying only the last 512k.
150        if not output_limit:
151            output_limit = 512 * 1024  # 512k
152        output = script_error.message_with_output(output_limit=output_limit)
153        # We pre-encode the string to a byte array before passing it
154        # to status_server, because ClientForm (part of mechanize)
155        # wants a file-like object with pre-encoded data.
156        return StringIO(output.encode("utf-8"))
157
158    @classmethod
159    def _update_status_for_script_error(cls, tool, state, script_error, is_error=False):
160        message = str(script_error)
161        if is_error:
162            message = "Error: %s" % message
163        failure_log = cls._log_from_script_error_for_upload(script_error)
164        return tool.status_server.update_status(cls.name, message, state["patch"], failure_log)
165
166
167class FeederQueue(AbstractQueue):
168    name = "feeder-queue"
169
170    _sleep_duration = 30  # seconds
171
172    # AbstractQueue methods
173
174    def begin_work_queue(self):
175        AbstractQueue.begin_work_queue(self)
176        self.feeders = [
177            CommitQueueFeeder(self._tool),
178            EWSFeeder(self._tool),
179        ]
180
181    def next_work_item(self):
182        # This really show inherit from some more basic class that doesn't
183        # understand work items, but the base class in the heirarchy currently
184        # understands work items.
185        return "synthetic-work-item"
186
187    def should_proceed_with_work_item(self, work_item):
188        return True
189
190    def process_work_item(self, work_item):
191        for feeder in self.feeders:
192            feeder.feed()
193        time.sleep(self._sleep_duration)
194        return True
195
196    def work_item_log_path(self, work_item):
197        return None
198
199    def handle_unexpected_error(self, work_item, message):
200        log(message)
201
202
203class AbstractPatchQueue(AbstractQueue):
204    def _update_status(self, message, patch=None, results_file=None):
205        return self._tool.status_server.update_status(self.name, message, patch, results_file)
206
207    def _next_patch(self):
208        patch_id = self._tool.status_server.next_work_item(self.name)
209        if not patch_id:
210            return None
211        patch = self._tool.bugs.fetch_attachment(patch_id)
212        if not patch:
213            # FIXME: Using a fake patch because release_work_item has the wrong API.
214            # We also don't really need to release the lock (although that's fine),
215            # mostly we just need to remove this bogus patch from our queue.
216            # If for some reason bugzilla is just down, then it will be re-fed later.
217            patch = Attachment({'id': patch_id}, None)
218            self._release_work_item(patch)
219            return None
220        return patch
221
222    def _release_work_item(self, patch):
223        self._tool.status_server.release_work_item(self.name, patch)
224
225    def _did_pass(self, patch):
226        self._update_status(self._pass_status, patch)
227        self._release_work_item(patch)
228
229    def _did_fail(self, patch):
230        self._update_status(self._fail_status, patch)
231        self._release_work_item(patch)
232
233    def _did_retry(self, patch):
234        self._update_status(self._retry_status, patch)
235        self._release_work_item(patch)
236
237    def _did_error(self, patch, reason):
238        message = "%s: %s" % (self._error_status, reason)
239        self._update_status(message, patch)
240        self._release_work_item(patch)
241
242    # FIXME: This probably belongs at a layer below AbstractPatchQueue, but shared by CommitQueue and the EarlyWarningSystem.
243    def _upload_results_archive_for_patch(self, patch, results_archive_zip):
244        bot_id = self._tool.status_server.bot_id or "bot"
245        description = "Archive of layout-test-results from %s" % bot_id
246        # results_archive is a ZipFile object, grab the File object (.fp) to pass to Mechanize for uploading.
247        results_archive_file = results_archive_zip.fp
248        # Rewind the file object to start (since Mechanize won't do that automatically)
249        # See https://bugs.webkit.org/show_bug.cgi?id=54593
250        results_archive_file.seek(0)
251        # FIXME: This is a small lie to always say run-webkit-tests since Chromium uses new-run-webkit-tests.
252        # We could make this code look up the test script name off the port.
253        comment_text = "The attached test failures were seen while running run-webkit-tests on the %s.\n" % (self.name)
254        # FIXME: We could easily list the test failures from the archive here,
255        # currently callers do that separately.
256        comment_text += BotInfo(self._tool).summary_text()
257        self._tool.bugs.add_attachment_to_bug(patch.bug_id(), results_archive_file, description, filename="layout-test-results.zip", comment_text=comment_text)
258
259    def work_item_log_path(self, patch):
260        return os.path.join(self._log_directory(), "%s.log" % patch.bug_id())
261
262
263class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskDelegate):
264    name = "commit-queue"
265
266    # AbstractPatchQueue methods
267
268    def begin_work_queue(self):
269        AbstractPatchQueue.begin_work_queue(self)
270        self.committer_validator = CommitterValidator(self._tool.bugs)
271        self._expected_failures = ExpectedFailures()
272        self._layout_test_results_reader = LayoutTestResultsReader(self._tool, self._log_directory())
273
274    def next_work_item(self):
275        return self._next_patch()
276
277    def should_proceed_with_work_item(self, patch):
278        patch_text = "rollout patch" if patch.is_rollout() else "patch"
279        self._update_status("Processing %s" % patch_text, patch)
280        return True
281
282    def process_work_item(self, patch):
283        self._cc_watchers(patch.bug_id())
284        task = CommitQueueTask(self, patch)
285        try:
286            if task.run():
287                self._did_pass(patch)
288                return True
289            self._did_retry(patch)
290        except ScriptError, e:
291            validator = CommitterValidator(self._tool.bugs)
292            validator.reject_patch_from_commit_queue(patch.id(), self._error_message_for_bug(task.failure_status_id, e))
293            results_archive = task.results_archive_from_patch_test_run(patch)
294            if results_archive:
295                self._upload_results_archive_for_patch(patch, results_archive)
296            self._did_fail(patch)
297
298    def _error_message_for_bug(self, status_id, script_error):
299        if not script_error.output:
300            return script_error.message_with_output()
301        results_link = self._tool.status_server.results_url_for_status(status_id)
302        return "%s\nFull output: %s" % (script_error.message_with_output(), results_link)
303
304    def handle_unexpected_error(self, patch, message):
305        self.committer_validator.reject_patch_from_commit_queue(patch.id(), message)
306
307    # CommitQueueTaskDelegate methods
308
309    def run_command(self, command):
310        self.run_webkit_patch(command)
311
312    def command_passed(self, message, patch):
313        self._update_status(message, patch=patch)
314
315    def command_failed(self, message, script_error, patch):
316        failure_log = self._log_from_script_error_for_upload(script_error)
317        return self._update_status(message, patch=patch, results_file=failure_log)
318
319    def expected_failures(self):
320        return self._expected_failures
321
322    def layout_test_results(self):
323        return self._layout_test_results_reader.results()
324
325    def archive_last_layout_test_results(self, patch):
326        return self._layout_test_results_reader.archive(patch)
327
328    def build_style(self):
329        return "both"
330
331    def refetch_patch(self, patch):
332        return self._tool.bugs.fetch_attachment(patch.id())
333
334    def report_flaky_tests(self, patch, flaky_test_results, results_archive=None):
335        reporter = FlakyTestReporter(self._tool, self.name)
336        reporter.report_flaky_tests(patch, flaky_test_results, results_archive)
337
338    # StepSequenceErrorHandler methods
339
340    def handle_script_error(cls, tool, state, script_error):
341        # Hitting this error handler should be pretty rare.  It does occur,
342        # however, when a patch no longer applies to top-of-tree in the final
343        # land step.
344        log(script_error.message_with_output())
345
346    @classmethod
347    def handle_checkout_needs_update(cls, tool, state, options, error):
348        message = "Tests passed, but commit failed (checkout out of date).  Updating, then landing without building or re-running tests."
349        tool.status_server.update_status(cls.name, message, state["patch"])
350        # The only time when we find out that out checkout needs update is
351        # when we were ready to actually pull the trigger and land the patch.
352        # Rather than spinning in the master process, we retry without
353        # building or testing, which is much faster.
354        options.build = False
355        options.test = False
356        options.update = True
357        raise TryAgain()
358
359
360class AbstractReviewQueue(AbstractPatchQueue, StepSequenceErrorHandler):
361    """This is the base-class for the EWS queues and the style-queue."""
362    def __init__(self, options=None):
363        AbstractPatchQueue.__init__(self, options)
364
365    def review_patch(self, patch):
366        raise NotImplementedError("subclasses must implement")
367
368    # AbstractPatchQueue methods
369
370    def begin_work_queue(self):
371        AbstractPatchQueue.begin_work_queue(self)
372
373    def next_work_item(self):
374        return self._next_patch()
375
376    def should_proceed_with_work_item(self, patch):
377        raise NotImplementedError("subclasses must implement")
378
379    def process_work_item(self, patch):
380        try:
381            if not self.review_patch(patch):
382                return False
383            self._did_pass(patch)
384            return True
385        except ScriptError, e:
386            if e.exit_code != QueueEngine.handled_error_code:
387                self._did_fail(patch)
388            else:
389                # The subprocess handled the error, but won't have released the patch, so we do.
390                # FIXME: We need to simplify the rules by which _release_work_item is called.
391                self._release_work_item(patch)
392            raise e
393
394    def handle_unexpected_error(self, patch, message):
395        log(message)
396
397    # StepSequenceErrorHandler methods
398
399    @classmethod
400    def handle_script_error(cls, tool, state, script_error):
401        log(script_error.message_with_output())
402
403
404class StyleQueue(AbstractReviewQueue):
405    name = "style-queue"
406    def __init__(self):
407        AbstractReviewQueue.__init__(self)
408
409    def should_proceed_with_work_item(self, patch):
410        self._update_status("Checking style", patch)
411        return True
412
413    def review_patch(self, patch):
414        self.run_webkit_patch(["check-style", "--force-clean", "--non-interactive", "--parent-command=style-queue", patch.id()])
415        self.run_webkit_patch(["apply-watchlist-local", patch.bug_id()])
416        return True
417
418    @classmethod
419    def handle_script_error(cls, tool, state, script_error):
420        is_svn_apply = script_error.command_name() == "svn-apply"
421        status_id = cls._update_status_for_script_error(tool, state, script_error, is_error=is_svn_apply)
422        if is_svn_apply:
423            QueueEngine.exit_after_handled_error(script_error)
424        message = "Attachment %s did not pass %s:\n\n%s\n\nIf any of these errors are false positives, please file a bug against check-webkit-style." % (state["patch"].id(), cls.name, script_error.message_with_output(output_limit=3*1024))
425        tool.bugs.post_comment_to_bug(state["patch"].bug_id(), message, cc=cls.watchers)
426        exit(1)
427
Full Screen

patch_mixin_tests.js

Source: patch_mixin_tests.js Github

copy
1odoo.define("web.patchMixin_tests", function (require) {
2"use strict";
3
4const patchMixin = require('web.patchMixin');
5
6QUnit.module('core', {}, function () {
7
8    QUnit.module('patchMixin', {}, function () {
9
10        QUnit.test('basic use', function (assert) {
11            assert.expect(4);
12
13            const A = patchMixin(
14                class {
15                    constructor() {
16                        assert.step('A.constructor');
17                    }
18                    f() {
19                        assert.step('A.f');
20                    }
21                }
22            );
23
24            const a = new A();
25            a.f();
26
27            assert.ok(a instanceof A);
28            assert.verifySteps([
29                'A.constructor',
30                'A.f',
31            ]);
32        });
33
34        QUnit.test('simple patch', function (assert) {
35            assert.expect(5);
36
37            const A = patchMixin(
38                class {
39                    constructor() {
40                        assert.step('A.constructor');
41                    }
42                    f() {
43                        assert.step('A.f');
44                    }
45                }
46            );
47
48            A.patch('patch', T =>
49                class extends T {
50                    constructor() {
51                        super();
52                        assert.step('patch.constructor');
53                    }
54                    f() {
55                        super.f();
56                        assert.step('patch.f');
57                    }
58                }
59            );
60
61            (new A()).f();
62
63            assert.verifySteps([
64                'A.constructor',
65                'patch.constructor',
66                'A.f',
67                'patch.f',
68            ]);
69        });
70
71        QUnit.test('two patches on same base class', function (assert) {
72            assert.expect(7);
73
74            const A = patchMixin(
75                class {
76                    constructor() {
77                        assert.step('A.constructor');
78                    }
79                    f() {
80                        assert.step('A.f');
81                    }
82                }
83            );
84
85            A.patch('patch1', T =>
86                class extends T {
87                    constructor() {
88                        super();
89                        assert.step('patch1.constructor');
90                    }
91                    f() {
92                        super.f();
93                        assert.step('patch1.f');
94                    }
95                }
96            );
97
98            A.patch('patch2', T =>
99                class extends T {
100                    constructor() {
101                        super();
102                        assert.step('patch2.constructor');
103                    }
104                    f() {
105                        super.f();
106                        assert.step('patch2.f');
107                    }
108                }
109            );
110
111            (new A()).f();
112
113            assert.verifySteps([
114                'A.constructor',
115                'patch1.constructor',
116                'patch2.constructor',
117                'A.f',
118                'patch1.f',
119                'patch2.f',
120            ]);
121        });
122
123        QUnit.test('two patches with same name on same base class', function (assert) {
124            assert.expect(1);
125
126            const A = patchMixin(class {});
127
128            A.patch('patch', T => class extends T {});
129
130            // keys should be unique
131            assert.throws(() => {
132                A.patch('patch', T => class extends T {});
133            });
134        });
135
136        QUnit.test('unpatch', function (assert) {
137            assert.expect(8);
138
139            const A = patchMixin(
140                class {
141                    constructor() {
142                        assert.step('A.constructor');
143                    }
144                    f() {
145                        assert.step('A.f');
146                    }
147                }
148            );
149
150            A.patch('patch', T =>
151                class extends T {
152                    constructor() {
153                        super();
154                        assert.step('patch.constructor');
155                    }
156                    f() {
157                        super.f();
158                        assert.step('patch.f');
159                    }
160                }
161            );
162
163            (new A()).f();
164
165            assert.verifySteps([
166                'A.constructor',
167                'patch.constructor',
168                'A.f',
169                'patch.f',
170            ]);
171
172            A.unpatch('patch');
173
174            (new A()).f();
175
176            assert.verifySteps([
177                'A.constructor',
178                'A.f',
179            ]);
180        });
181
182        QUnit.test('unpatch 2', function (assert) {
183            assert.expect(12);
184
185            const A = patchMixin(
186                class {
187                    constructor() {
188                        assert.step('A.constructor');
189                    }
190                    f() {
191                        assert.step('A.f');
192                    }
193                }
194            );
195
196            A.patch('patch1', T =>
197                class extends T {
198                    constructor() {
199                        super();
200                        assert.step('patch1.constructor');
201                    }
202                    f() {
203                        super.f();
204                        assert.step('patch1.f');
205                    }
206                }
207            );
208
209            A.patch('patch2', T =>
210                class extends T {
211                    constructor() {
212                        super();
213                        assert.step('patch2.constructor');
214                    }
215                    f() {
216                        super.f();
217                        assert.step('patch2.f');
218                    }
219                }
220            );
221
222            (new A()).f();
223
224            assert.verifySteps([
225                'A.constructor',
226                'patch1.constructor',
227                'patch2.constructor',
228                'A.f',
229                'patch1.f',
230                'patch2.f',
231            ]);
232
233            A.unpatch('patch1');
234
235            (new A()).f();
236
237            assert.verifySteps([
238                'A.constructor',
239                'patch2.constructor',
240                'A.f',
241                'patch2.f',
242            ]);
243        });
244
245        QUnit.test('unpatch inexistent', function (assert) {
246            assert.expect(1);
247
248            const A = patchMixin(class {});
249            A.patch('patch', T => class extends T {});
250
251            A.unpatch('patch');
252            assert.throws(() => {
253                A.unpatch('inexistent-patch');
254            });
255        });
256
257        QUnit.test('patch for specialization', function (assert) {
258            assert.expect(1);
259
260            let args = [];
261
262            const A = patchMixin(
263                class {
264                    constructor() {
265                        args = ['A', ...arguments];
266                    }
267                }
268            );
269
270            A.patch('patch', T =>
271                class extends T {
272                    constructor() {
273                        super('patch', ...arguments);
274                    }
275                }
276            );
277
278            new A('instantiation');
279
280            assert.deepEqual(args, ['A', 'patch', 'instantiation']);
281        });
282
283        QUnit.test('instance fields', function (assert) {
284            assert.expect(1);
285
286            const A = patchMixin(
287                class {
288                    constructor() {
289                        this.x = ['A'];
290                    }
291                }
292            );
293
294            A.patch('patch', T =>
295                class extends T {
296                    constructor() {
297                        super();
298                        this.x.push('patch');
299                    }
300                }
301            );
302
303            const a = new A();
304            assert.deepEqual(a.x, ['A', 'patch']);
305        });
306
307        QUnit.test('call instance method defined in patch', function (assert) {
308            assert.expect(3);
309
310            const A = patchMixin(
311                class {}
312            );
313
314            assert.notOk((new A()).f);
315
316            A.patch('patch', T =>
317                class extends T {
318                    f() {
319                        assert.step('patch.f');
320                    }
321                }
322            );
323
324            (new A()).f();
325            assert.verifySteps(['patch.f']);
326        });
327
328        QUnit.test('class methods', function (assert) {
329            assert.expect(7);
330
331            const A = patchMixin(
332                class {
333                    static f() {
334                        assert.step('A');
335                    }
336                }
337            );
338
339            A.f();
340            assert.verifySteps(['A']);
341
342            A.patch('patch', T =>
343                class extends T {
344                    static f() {
345                        super.f();
346                        assert.step('patch');
347                    }
348                }
349            );
350
351            A.f();
352            assert.verifySteps(['A', 'patch']);
353
354            A.unpatch('patch');
355
356            A.f();
357            assert.verifySteps(['A']);
358        });
359
360        QUnit.test('class fields', function (assert) {
361            assert.expect(4);
362
363            class A {}
364            A.foo = ['A'];
365            A.bar = 'A';
366
367            const PatchableA = patchMixin(A);
368
369            PatchableA.patch('patch', T => {
370                class Patch extends T {}
371
372                Patch.foo = [...T.foo, 'patched A'];
373                Patch.bar = 'patched A';
374
375                return Patch;
376            });
377
378            assert.deepEqual(PatchableA.foo, ['A', 'patched A']);
379            assert.strictEqual(PatchableA.bar, 'patched A');
380
381            PatchableA.unpatch('patch');
382
383            assert.deepEqual(PatchableA.foo, ['A']);
384            assert.strictEqual(PatchableA.bar, 'A');
385        });
386
387        QUnit.test('lazy patch', function (assert) {
388            assert.expect(4);
389
390            const A = patchMixin(
391                class {
392                    constructor() {
393                        assert.step('A.constructor');
394                    }
395                    f() {
396                        assert.step('A.f');
397                    }
398                }
399            );
400
401            const a = new A();
402
403            A.patch('patch', T =>
404                class extends T {
405                    constructor() {
406                        super();
407                        // will not be called
408                        assert.step('patch.constructor');
409                    }
410                    f() {
411                        super.f();
412                        assert.step('patch.f');
413                    }
414                }
415            );
416
417            a.f();
418
419            assert.verifySteps([
420                'A.constructor',
421                'A.f',
422                'patch.f',
423            ]);
424        });
425
426
427        QUnit.module('inheritance');
428
429        QUnit.test('inheriting a patchable class', function (assert) {
430            assert.expect(8);
431
432            const A = patchMixin(
433                class {
434                    constructor() {
435                        assert.step('A.constructor');
436                    }
437                    f() {
438                        assert.step('A.f');
439                    }
440                }
441            );
442
443            class B extends A {
444                constructor() {
445                    super();
446                    assert.step('B.constructor');
447                }
448                f() {
449                    super.f();
450                    assert.step('B.f');
451                }
452            }
453
454            (new A()).f();
455
456            assert.verifySteps([
457                'A.constructor',
458                'A.f',
459            ]);
460
461            (new B()).f();
462
463            assert.verifySteps([
464                'A.constructor',
465                'B.constructor',
466                'A.f',
467                'B.f',
468            ]);
469        });
470
471        QUnit.test('inheriting a patchable class that has patch', function (assert) {
472            assert.expect(12);
473
474            const A = patchMixin(
475                class {
476                    constructor() {
477                        assert.step('A.constructor');
478                    }
479                    f() {
480                        assert.step('A.f');
481                    }
482                }
483            );
484
485            A.patch('patch', T =>
486                class extends T {
487                    constructor() {
488                        super();
489                        assert.step('patch.constructor');
490                    }
491                    f() {
492                        super.f();
493                        assert.step('patch.f');
494                    }
495                }
496            );
497
498            class B extends A {
499                constructor() {
500                    super();
501                    assert.step('B.constructor');
502                }
503                f() {
504                    super.f();
505                    assert.step('B.f');
506                }
507            }
508
509            (new A()).f();
510
511            assert.verifySteps([
512                'A.constructor',
513                'patch.constructor',
514                'A.f',
515                'patch.f',
516            ]);
517
518            (new B()).f();
519
520            assert.verifySteps([
521                'A.constructor',
522                'patch.constructor',
523                'B.constructor',
524                'A.f',
525                'patch.f',
526                'B.f',
527            ]);
528        });
529
530        QUnit.test('patch inherited patchable class', function (assert) {
531            assert.expect(10);
532
533            const A = patchMixin(
534                class {
535                    constructor() {
536                        assert.step('A.constructor');
537                    }
538                    f() {
539                        assert.step('A.f');
540                    }
541                }
542            );
543
544            const B = patchMixin(
545                class extends A {
546                    constructor() {
547                        super();
548                        assert.step('B.constructor');
549                    }
550                    f() {
551                        super.f();
552                        assert.step('B.f');
553                    }
554                }
555            );
556
557            B.patch('patch', T =>
558                class extends T {
559                    constructor() {
560                        super();
561                        assert.step('patch.constructor');
562                    }
563                    f() {
564                        super.f();
565                        assert.step('patch.f');
566                    }
567                }
568            );
569
570            (new A()).f();
571
572            assert.verifySteps([
573                'A.constructor',
574                'A.f',
575            ]);
576
577            (new B()).f();
578
579            assert.verifySteps([
580                'A.constructor',
581                'B.constructor',
582                'patch.constructor',
583                'A.f',
584                'B.f',
585                'patch.f',
586            ]);
587        });
588
589        QUnit.test('patch inherited patched class', function (assert) {
590            assert.expect(14);
591
592            const A = patchMixin(
593                class {
594                    constructor() {
595                        assert.step('A.constructor');
596                    }
597                    f() {
598                        assert.step('A.f');
599                    }
600                }
601            );
602
603            A.patch('patch', T =>
604                class extends T {
605                    constructor() {
606                        super();
607                        assert.step('A.patch.constructor');
608                    }
609                    f() {
610                        super.f();
611                        assert.step('A.patch.f');
612                    }
613                }
614            );
615
616            /**
617             * /!\ WARNING /!\
618             *
619             * If you want to patch class B, make it patchable
620             * otherwise it will patch class A!
621             */
622            const B = patchMixin(
623                class extends A {
624                    constructor() {
625                        super();
626                        assert.step('B.constructor');
627                    }
628                    f() {
629                        super.f();
630                        assert.step('B.f');
631                    }
632                }
633            );
634
635            B.patch('patch', T =>
636                class extends T {
637                    constructor() {
638                        super();
639                        assert.step('B.patch.constructor');
640                    }
641                    f() {
642                        super.f();
643                        assert.step('B.patch.f');
644                    }
645                }
646            );
647
648            const a = new A();
649            a.f();
650
651            assert.verifySteps([
652                'A.constructor',
653                'A.patch.constructor',
654                'A.f',
655                'A.patch.f',
656            ]);
657
658            const b = new B();
659            b.f();
660
661            assert.verifySteps([
662                'A.constructor',
663                'A.patch.constructor',
664                'B.constructor',
665                'B.patch.constructor',
666                'A.f',
667                'A.patch.f',
668                'B.f',
669                'B.patch.f',
670            ]);
671        });
672
673        QUnit.test('unpatch inherited patched class', function (assert) {
674            assert.expect(15);
675
676            const A = patchMixin(
677                class {
678                    constructor() {
679                        assert.step('A.constructor');
680                    }
681                    f() {
682                        assert.step('A.f');
683                    }
684                }
685            );
686
687            A.patch('patch', T =>
688                class extends T {
689                    constructor() {
690                        super();
691                        assert.step('A.patch.constructor');
692                    }
693                    f() {
694                        super.f();
695                        assert.step('A.patch.f');
696                    }
697                }
698            );
699
700            const B = patchMixin(
701                class extends A {
702                    constructor() {
703                        super();
704                        assert.step('B.constructor');
705                    }
706                    f() {
707                        super.f();
708                        assert.step('B.f');
709                    }
710                }
711            );
712
713            B.patch('patch', T =>
714                class extends T {
715                    constructor() {
716                        super();
717                        assert.step('B.patch.constructor');
718                    }
719                    f() {
720                        super.f();
721                        assert.step('B.patch.f');
722                    }
723                }
724            );
725
726            A.unpatch('patch');
727
728            (new A()).f();
729
730            assert.verifySteps([
731                'A.constructor',
732                'A.f',
733            ]);
734
735            (new B()).f();
736
737            assert.verifySteps([
738                'A.constructor',
739                'B.constructor',
740                'B.patch.constructor',
741                'A.f',
742                'B.f',
743                'B.patch.f',
744            ]);
745
746            B.unpatch('patch');
747
748            (new B()).f();
749
750            assert.verifySteps([
751                'A.constructor',
752                'B.constructor',
753                'A.f',
754                'B.f',
755            ]);
756        });
757
758        QUnit.test('unpatch inherited patched class 2', function (assert) {
759            assert.expect(12);
760
761            const A = patchMixin(
762                class {
763                    constructor() {
764                        assert.step('A.constructor');
765                    }
766                    f() {
767                        assert.step('A.f');
768                    }
769                }
770            );
771
772            A.patch('patch', T =>
773                class extends T {
774                    constructor() {
775                        super();
776                        assert.step('A.patch.constructor');
777                    }
778                    f() {
779                        super.f();
780                        assert.step('A.patch.f');
781                    }
782                }
783            );
784
785            const B = patchMixin(
786                class extends A {
787                    constructor() {
788                        super();
789                        assert.step('B.constructor');
790                    }
791                    f() {
792                        super.f();
793                        assert.step('B.f');
794                    }
795                }
796            );
797
798            B.patch('patch', T =>
799                class extends T {
800                    constructor() {
801                        super();
802                        assert.step('B.patch.constructor');
803                    }
804                    f() {
805                        super.f();
806                        assert.step('B.patch.f');
807                    }
808                }
809            );
810
811            B.unpatch('patch');
812
813            (new B()).f();
814
815            assert.verifySteps([
816                'A.constructor',
817                'A.patch.constructor',
818                'B.constructor',
819                'A.f',
820                'A.patch.f',
821                'B.f',
822            ]);
823
824            A.unpatch('patch');
825
826            (new B()).f();
827
828            assert.verifySteps([
829                'A.constructor',
830                'B.constructor',
831                'A.f',
832                'B.f',
833            ]);
834        });
835
836        QUnit.test('class methods', function (assert) {
837            assert.expect(12);
838
839            const A = patchMixin(
840                class {
841                    static f() {
842                        assert.step('A');
843                    }
844                }
845            );
846
847            const B = patchMixin(
848                class extends A {
849                    static f() {
850                        super.f();
851                        assert.step('B');
852                    }
853                }
854            );
855
856            A.patch('patch', T =>
857                class extends T {
858                    static f() {
859                        super.f();
860                        assert.step('A.patch');
861                    }
862                }
863            );
864
865            B.patch('patch', T =>
866                class extends T {
867                    static f() {
868                        super.f();
869                        assert.step('B.patch');
870                    }
871                }
872            );
873
874            B.f();
875            assert.verifySteps(['A', 'A.patch', 'B', 'B.patch']);
876
877            A.unpatch('patch');
878
879            B.f();
880            assert.verifySteps(['A', 'B', 'B.patch']);
881
882            B.unpatch('patch');
883
884            B.f();
885            assert.verifySteps(['A', 'B']);
886        });
887
888        QUnit.test('class fields', function (assert) {
889            assert.expect(3);
890
891            class A {}
892            A.foo = ['A'];
893            A.bar = 'A';
894
895            const PatchableA = patchMixin(A);
896
897            class B extends PatchableA {}
898            // /!\ This is not dynamic
899            // so if A.foo is patched after this assignment
900            // B.foo won't have the patches of A.foo
901            B.foo = [...PatchableA.foo, 'B'];
902            B.bar = 'B';
903
904            const PatchableB = patchMixin(B);
905
906            PatchableA.patch('patch', T => {
907                class Patch extends T {}
908
909                Patch.foo = [...T.foo, 'patched A'];
910                Patch.bar = 'patched A';
911
912                return Patch;
913            });
914
915            PatchableB.patch('patch', T => {
916                class Patch extends T {}
917
918                Patch.foo = [...T.foo, 'patched B'];
919                Patch.bar = 'patched B';
920
921                return Patch;
922            });
923
924            assert.deepEqual(PatchableB.foo, [ 'A', /* 'patched A', */ 'B', 'patched B' ]);
925            assert.deepEqual(PatchableA.foo, [ 'A', 'patched A' ]);
926            assert.strictEqual(PatchableB.bar, 'patched B');
927        });
928
929        QUnit.test('inheritance and lazy patch', function (assert) {
930            assert.expect(6);
931
932            const A = patchMixin(
933                class {
934                    constructor() {
935                        assert.step('A.constructor');
936                    }
937                    f() {
938                        assert.step('A.f');
939                    }
940                }
941            );
942
943            class B extends A {
944                constructor() {
945                    super();
946                    assert.step('B.constructor');
947                }
948                f() {
949                    super.f();
950                    assert.step('B.f');
951                }
952            }
953
954            const b = new B();
955
956            A.patch('patch', T =>
957                class extends T {
958                    constructor() {
959                        super();
960                        // will not be called
961                        assert.step('patch.constructor');
962                    }
963                    f() {
964                        super.f();
965                        assert.step('patch.f');
966                    }
967                }
968            );
969
970            b.f();
971
972            assert.verifySteps([
973                'A.constructor',
974                'B.constructor',
975                'A.f',
976                'patch.f',
977                'B.f',
978            ]);
979        });
980
981        QUnit.test('patch not patchable class that inherits patchable class', function (assert) {
982            assert.expect(1);
983
984            const A = patchMixin(class {});
985            class B extends A {}
986
987            // class B is not patchable
988            assert.throws(() => {
989                B.patch('patch', T => class extends T {});
990            });
991        });
992    });
993});
994});
995
Full Screen