1+ r""" 
2+ There are two main classes used testing during the upgrade. 
3+ 
4+ * :class:`~odoo.upgrade.testing.UpgradeCase` for testing upgrade scripts, 
5+ * :class:`~odoo.upgrade.testing.UpgradeCase` for testing invariants across versions. 
6+ 
7+ Subclasses must implement: 
8+ 
9+ * For ``UpgradeCase``: 
10+ 
11+   - ``prepare`` method: prepare data before upgrade, 
12+   - ``check`` method: check data was correctly upgraded. 
13+ 
14+ * For ``IntegrityCase``: 
15+ 
16+   - ``invariant`` method: compute an invariant to check. 
17+ 
18+ Put your test classes in a ``tests`` Python module (folder) in any of the folders 
19+ containing the upgrade scripts of your modules. The script containing your tests must 
20+ have a `test_` prefix. The ``tests`` module must contain an ``__init__.py`` file to be 
21+ detected by Odoo. 
22+ 
23+ Example directory structure:: 
24+ 
25+    myupgrades/ 
26+    └── mymodule1/ 
27+        ├── 18.0.1.1.2/ 
28+        │   └── pre-myupgrade.py 
29+        └── tests/ 
30+            ├── __init__.py 
31+            └── test_myupgrade.py 
32+ 
33+ .. note:: 
34+ 
35+    The tests in the example above will be loaded only if ``mymodule1`` is being 
36+    **upgraded.** 
37+ 
38+ Running Upgrade Tests 
39+ --------------------- 
40+ 
41+ After receiving an upgraded database with all standard Odoo modules already upgraded to 
42+ their target version, you can test the upgrade of custom modules by following a three-step 
43+ process: 
44+ 
45+ 1. Prepare the test data 
46+ 
47+    .. code-block:: bash 
48+ 
49+       $ ~/odoo/$version/odoo-bin -d DB --test-tags=$prepare_test_tag \ 
50+         --upgrade-path=~/upgrade-util/src,~/myupgrades \ 
51+         --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop 
52+ 
53+ 2. Upgrade the modules 
54+ 
55+    .. code-block:: bash 
56+ 
57+       $ ~/odoo/$version/odoo-bin -d DB -u mymodule1,mymodule2 \ 
58+         --upgrade-path=~/upgrade-util/src,~/myupgrades \ 
59+         --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop 
60+ 
61+ 3. Check the upgraded data 
62+ 
63+    .. code-block:: bash 
64+ 
65+       $ ~/odoo/$version/odoo-bin -d DB --test-tags=$check_test_tag \ 
66+         --upgrade-path=~/upgrade-util/src,~/myupgrades \ 
67+         --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop 
68+ 
69+ The example above assumes that ``$version`` is the target version of your upgrade (e.g. 
70+ ``18.0``), ``DB`` is the name of your database, and ``mymodule1,mymodule2`` are the 
71+ modules you want to upgrade. The directory structure assumes that ``~/odoo/$version`` and 
72+ ``~/enterprise/$version`` contain the Community and Enterprise source code for the target 
73+ Odoo version, respectively. ``~/mymodules`` contains the code of your custom modules 
74+ (``mymodule1``, ...), ``~/myupgrades`` contains your custom upgrade scripts, and 
75+ ``~/upgrade-util`` contains the `upgrade utils <https://github.com/odoo/upgrade-util/>`_ 
76+ repo. 
77+ 
78+ The variables ``$prepare_test_tag`` and ``$check_test_tag`` must be set according to: 
79+ 
80+ .. list-table:: 
81+    :header-rows: 1 
82+    :stub-columns: 1 
83+ 
84+    * - Variable 
85+      - ``UpgradeCase`` 
86+      - ``IntegrityCase`` 
87+    * - ``$prepare_test_tag`` 
88+      - ``upgrade.test_prepare`` 
89+      - ``integrity_case.test_prepare`` 
90+    * - ``$check_test_tag`` 
91+      - ``upgrade.test_check`` 
92+      - ``integrity_test.test_check`` 
93+ 
94+ .. note:: 
95+ 
96+    `upgrade.test_prepare` also runs ``IntegrityCase`` tests, so you can prepare data 
97+    for both ``UpgradeCase`` and ``IntegrityCase`` tests with only this tag. 
98+ 
99+ .. warning:: 
100+ 
101+    Do **not** run any ``prepare`` method of an ``UpgradeCase`` before sending your 
102+    database for a **production** upgrade to `upgrade.odoo.com 
103+    <https://upgrade.odoo.com>`_. Doing so may risk your upgrade being blocked and marked 
104+    as failed. 
105+ 
106+ API documentation 
107+ ----------------- 
108+ """ 
109+ 
1110import  functools 
2111import  inspect 
3112import  logging 
@@ -40,22 +149,17 @@ def parametrize(argvalues):
40149    """ 
41150    Parametrize a test function. 
42151
43-     Decorator for UnitTestCase test functions to parametrize the decorated test. 
44- 
45-     Usage: 
46-     ```python 
47-     @parametrize([ 
48-         (1, 2), 
49-         (2, 4), 
50-         (-1, -2), 
51-         (0, 0), 
52-     ]) 
53-     def test_double(self, input, expected): 
54-         self.assertEqual(input * 2, expected) 
55-     ``` 
56- 
57-     It works by injecting test functions in the containing class. 
58-     Idea taken from the `parameterized` package (https://pypi.org/project/parameterized/). 
152+     Decorator for upgrade test functions to parametrize and generate multiple tests from 
153+     it. 
154+ 
155+     Usage:: 
156+ 
157+         @parametrize([(1, 2), (2, 4), (-1, -2), (0, 0)]) 
158+         def test_double(self, input, expected): 
159+             self.assertEqual(input * 2, expected) 
160+ 
161+     Works by injecting test functions in the containing class. 
162+     Inspired by the `parameterized <https://pypi.org/project/parameterized/>`_ package. 
59163    """ 
60164
61165    def  make_func (func , name , args ):
@@ -116,6 +220,8 @@ def __init_subclass__(cls):
116220
117221
118222class  UnitTestCase (TransactionCase , _create_meta (10 , "upgrade_unit" )):
223+     """:meta private: exclude from online docs.""" 
224+ 
119225    @classmethod  
120226    def  setUpClass (cls ):
121227        super ().setUpClass ()
@@ -211,6 +317,8 @@ def assertUpdated(self, table, ids=None, msg=None):
211317
212318
213319class  UpgradeCommon (BaseCase ):
320+     """:meta private: exclude from online docs.""" 
321+ 
214322    __initialized  =  False 
215323
216324    change_version  =  (None , None )
@@ -356,6 +464,25 @@ def convert_check(self, value):
356464
357465
358466def  change_version (version_str ):
467+     """ 
468+     Class decorator to specify the version on which a test is relevant. 
469+ 
470+     Using ``@change_version(version)`` indicates: 
471+ 
472+     * ``test_prepare`` will only run if the current Odoo version is in the range 
473+       ``[next_major_version-1, version)``. 
474+     * ``test_check`` will only run if the current Odoo version is in the range ``[version, 
475+       next_major_version)``. 
476+ 
477+     ``next_major_version`` is the next major version after ``version``, e.g. for 
478+     ``saas~17.2`` it is ``18.0``. 
479+ 
480+     .. note:: 
481+ 
482+        Do not use this decorator if your upgrade is in the same major version. Otherwise, 
483+        your tests will not run. 
484+     """ 
485+ 
359486    def  version_decorator (obj ):
360487        match  =  VERSION_RE .match (version_str )
361488        if  not  match :
@@ -394,24 +521,42 @@ def get_previous_major(major, minor):
394521# pylint: disable=inherit-non-class 
395522class  UpgradeCase (UpgradeCommon , _create_meta (10 , "upgrade_case" )):
396523    """ 
397-     Test case to modify data in origin version, and assert in target version . 
524+     Test case to verify that the upgrade scripts correctly upgrade data . 
398525
399-     User must define a "prepare" and a "check" method. 
400-     - prepare method can write in database, return value will be stored in a dedicated table and 
401-       passed as argument to check. 
402-     - check method can assert that the received argument is the one expected, 
403-       executing any code to retrieve equivalent information in migrated database. 
404-       Note: check argument is a loaded json dump, meaning that tuple are converted to list. 
405-       convert_check can be used to normalise the right part of the comparison. 
526+     Override: 
406527
407-     check method is only called if corresponding prepared was run in previous version 
528+     * ``prepare`` to set up data, 
529+     * ``check`` to assert expectations after the upgrade. 
408530
409-     prepare and check implementation may contains version conditional code to match api changes. 
531+     The ORM can be used in these methods to perform the functional flow under test. The 
532+     return value of ``prepare`` is persisted and passed as an argument to ``check``. It 
533+     must be JSON-serializable. 
410534
411-     using @change_version class decorator can indicate with script version is tested here if any: 
412-     Example: to test a saas~12.3 script, using @change_version('saas-12,3') will only run prepare if 
413-     version in [12.0, 12.3[ and run check if version is in [12.3, 13] 
535+     .. note:: 
414536
537+        Since ``prepare`` injects or modifies data, this type of test is intended **only 
538+        for development**. Use it to test upgrade scripts while developing them. **Do not** 
539+        run these tests for a production upgrade. To verify that upgrades preserved 
540+        important invariants in production, use ``IntegrityCase`` tests instead. 
541+ 
542+     .. example:: 
543+ 
544+        .. code-block:: python 
545+ 
546+           from odoo.upgrade.testing import UpgradeCase, change_version 
547+ 
548+ 
549+           class DeactivateBobUsers(UpgradeCase): 
550+ 
551+               def prepare(self): 
552+                   u = self.env["res.users"].create({"login": "bob", "name": "Bob"}) 
553+                   return u.id  # will be passed to check 
554+ 
555+               def check(self, uid):  # uid is the value returned by prepare 
556+                   self.env.cr.execute( 
557+                       "SELECT * FROM res_users WHERE id=%s AND NOT active", [uid] 
558+                   ) 
559+                   self.assertEqual(self.env.cr.rowcount, 1) 
415560    """ 
416561
417562    def  __init_subclass__ (cls , abstract = False ):
@@ -427,12 +572,27 @@ def test_prepare(self):
427572# pylint: disable=inherit-non-class 
428573class  IntegrityCase (UpgradeCommon , _create_meta (20 , "integrity_case" )):
429574    """ 
430-     Test case to check invariant through any version. 
575+     Test case for validating invariants across upgrades. 
576+ 
577+     Override: 
578+ 
579+     * ``invariant`` to return a JSON-serializable value representing 
580+       the invariant to check. 
581+ 
582+     The ``invariant`` method is called both before and after the upgrade, 
583+     and the results are compared. 
584+ 
585+ 
586+     .. example:: 
587+ 
588+        .. code-block:: python 
589+ 
590+           from odoo.upgrade.testing import IntegrityCase 
431591
432-     User must define a "invariant" method. 
433-     invariant return value will be compared between the two version. 
434592
435-     invariant implementation may contains version conditional code to match api changes. 
593+           class NoNewUsers(IntegrityCase): 
594+               def invariant(self): 
595+                   return self.env["res.users"].search_count([]) 
436596    """ 
437597
438598    message  =  "Invariant check fail" 
@@ -442,7 +602,7 @@ def __init_subclass__(cls, abstract=False):
442602        if  not  abstract  and  not  hasattr (cls , "invariant" ):
443603            _logger .error ("%s (IntegrityCase) must define an invariant method" , cls .__name__ )
444604
445-     # IntegrityCase should not alterate  database: 
605+     # IntegrityCase should not alter the  database: 
446606    # TODO give a test cursor, don't commit after prepare, use a protected cursor to set_value 
447607
448608    def  prepare (self ):
@@ -462,6 +622,7 @@ def _setup_registry(self):
462622            self .addCleanup (self .registry .leave_test_mode )
463623
464624    def  setUp (self ):
625+         """:meta private: exclude from online docs.""" 
465626        super (IntegrityCase , self ).setUp ()
466627
467628        def  commit (self ):
0 commit comments