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 should
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 --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 --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 --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. The ``~/myupgrades`` directory contains your custom upgrade
74
+ scripts, and ``~/upgrade-util/src`` contains the `upgrade utils
75
+ <https://github.com/odoo/upgrade-util/>`_ repo.
76
+
77
+ The variables ``$prepare_test_tag`` and ``$check_test_tag`` must be set according to:
78
+
79
+ .. list-table::
80
+ :header-rows: 1
81
+ :stub-columns: 1
82
+
83
+ * - Variable
84
+ - ``UpgradeCase``
85
+ - ``IntegrityCase``
86
+ * - ``$prepare_test_tag``
87
+ - ``upgrade.test_prepare``
88
+ - ``integrity_case.test_prepare``
89
+ * - ``$check_test_tag``
90
+ - ``upgrade.test_check``
91
+ - ``integrity_test.test_check``
92
+
93
+ .. note::
94
+
95
+ `upgrade.test_prepare` also runs ``IntegrityCase`` tests, so you can prepare data
96
+ for both ``UpgradeCase`` and ``IntegrityCase`` tests with only this tag.
97
+
98
+ .. warning::
99
+
100
+ Do **not** run any ``prepare`` method before sending your database for upgrade to
101
+ `upgrade.odoo.com <https://upgrade.odoo.com>`_. Doing so may cause conflicts with the
102
+ tests performed during the major upgrade.
103
+
104
+ API documentation
105
+ -----------------
106
+ """
107
+
1
108
import functools
2
109
import inspect
3
110
import logging
@@ -40,22 +147,17 @@ def parametrize(argvalues):
40
147
"""
41
148
Parametrize a test function.
42
149
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/).
150
+ Decorator for upgrade test functions to parametrize and generate multiple tests from
151
+ it.
152
+
153
+ Usage::
154
+
155
+ @parametrize([(1, 2), (2, 4), (-1, -2), (0, 0)])
156
+ def test_double(self, input, expected):
157
+ self.assertEqual(input * 2, expected)
158
+
159
+ Works by injecting test functions in the containing class.
160
+ Inspired by the `parameterized <https://pypi.org/project/parameterized/>`_ package.
59
161
"""
60
162
61
163
def make_func (func , name , args ):
@@ -116,6 +218,8 @@ def __init_subclass__(cls):
116
218
117
219
118
220
class UnitTestCase (TransactionCase , _create_meta (10 , "upgrade_unit" )):
221
+ """:meta private: exclude from online docs."""
222
+
119
223
@classmethod
120
224
def setUpClass (cls ):
121
225
super ().setUpClass ()
@@ -211,6 +315,8 @@ def assertUpdated(self, table, ids=None, msg=None):
211
315
212
316
213
317
class UpgradeCommon (BaseCase ):
318
+ """:meta private: exclude from online docs."""
319
+
214
320
__initialized = False
215
321
216
322
change_version = (None , None )
@@ -332,6 +438,25 @@ def convert_check(self, value):
332
438
333
439
334
440
def change_version (version_str ):
441
+ """
442
+ Class decorator to specify the version on which a test is relevant.
443
+
444
+ Using ``@change_version(version)`` indicates:
445
+
446
+ * ``test_prepare`` will only run if the current Odoo version is in the range
447
+ ``[next_major_version-1, version)``.
448
+ * ``test_check`` will only run if the current Odoo version is in the range ``[version,
449
+ next_major_version)``.
450
+
451
+ ``next_major_version`` is the next major version after ``version``, e.g. for
452
+ ``saas~17.2`` it is ``18.0``.
453
+
454
+ .. note::
455
+
456
+ Do not use this decorator if your upgrade is in the same major version. Otherwise,
457
+ your tests will not run.
458
+ """
459
+
335
460
def version_decorator (obj ):
336
461
match = VERSION_RE .match (version_str )
337
462
if not match :
@@ -370,24 +495,42 @@ def get_previous_major(major, minor):
370
495
# pylint: disable=inherit-non-class
371
496
class UpgradeCase (UpgradeCommon , _create_meta (10 , "upgrade_case" )):
372
497
"""
373
- Test case to modify data in origin version, and assert in target version .
498
+ Test case to verify that the upgrade scripts correctly upgrade data .
374
499
375
- User must define a "prepare" and a "check" method.
376
- - prepare method can write in database, return value will be stored in a dedicated table and
377
- passed as argument to check.
378
- - check method can assert that the received argument is the one expected,
379
- executing any code to retrieve equivalent information in migrated database.
380
- Note: check argument is a loaded json dump, meaning that tuple are converted to list.
381
- convert_check can be used to normalise the right part of the comparison.
500
+ Override:
382
501
383
- check method is only called if corresponding prepared was run in previous version
502
+ * ``prepare`` to set up data,
503
+ * ``check`` to assert expectations after the upgrade.
384
504
385
- prepare and check implementation may contains version conditional code to match api changes.
505
+ The ORM can be used in these methods to perform the functional flow under test. The
506
+ return value of ``prepare`` is persisted and passed as an argument to ``check``. It
507
+ must be JSON-serializable.
386
508
387
- using @change_version class decorator can indicate with script version is tested here if any:
388
- Example: to test a saas~12.3 script, using @change_version('saas-12,3') will only run prepare if
389
- version in [12.0, 12.3[ and run check if version is in [12.3, 13]
509
+ .. note::
390
510
511
+ Since ``prepare`` injects or modifies data, this type of test is intended **only
512
+ for development**. Use it to test upgrade scripts while developing them. **Do not**
513
+ run these tests for a production upgrade. To verify that upgrades preserved
514
+ important invariants in production, use ``IntegrityCase`` tests instead.
515
+
516
+ .. example::
517
+
518
+ .. code-block:: python
519
+
520
+ from odoo.upgrade.testing import UpgradeCase, change_version
521
+
522
+
523
+ class DeactivateBobUsers(UpgradeCase):
524
+
525
+ def prepare(self):
526
+ u = self.env["res.users"].create({"login": "bob", "name": "Bob"})
527
+ return u.id # will be passed to check
528
+
529
+ def check(self, uid): # uid is the value returned by prepare
530
+ self.env.cr.execute(
531
+ "SELECT * FROM res_users WHERE id=%s AND NOT active", [uid]
532
+ )
533
+ self.assertEqual(self.env.cr.rowcount, 1)
391
534
"""
392
535
393
536
def __init_subclass__ (cls , abstract = False ):
@@ -403,12 +546,27 @@ def test_prepare(self):
403
546
# pylint: disable=inherit-non-class
404
547
class IntegrityCase (UpgradeCommon , _create_meta (20 , "integrity_case" )):
405
548
"""
406
- Test case to check invariant through any version.
549
+ Test case for validating invariants across upgrades.
550
+
551
+ Override:
552
+
553
+ * ``invariant`` to return a JSON-serializable value representing
554
+ the invariant to check.
555
+
556
+ The ``invariant`` method is called both before and after the upgrade,
557
+ and the results are compared.
558
+
559
+
560
+ .. example::
561
+
562
+ .. code-block:: python
563
+
564
+ from odoo.upgrade.testing import IntegrityCase
407
565
408
- User must define a "invariant" method.
409
- invariant return value will be compared between the two version.
410
566
411
- invariant implementation may contains version conditional code to match api changes.
567
+ class NoNewUsers(IntegrityCase):
568
+ def invariant(self):
569
+ return self.env["res.users"].search_count([])
412
570
"""
413
571
414
572
message = "Invariant check fail"
@@ -418,7 +576,7 @@ def __init_subclass__(cls, abstract=False):
418
576
if not abstract and not hasattr (cls , "invariant" ):
419
577
_logger .error ("%s (IntegrityCase) must define an invariant method" , cls .__name__ )
420
578
421
- # IntegrityCase should not alterate database:
579
+ # IntegrityCase should not alter the database:
422
580
# TODO give a test cursor, don't commit after prepare, use a protected cursor to set_value
423
581
424
582
def prepare (self ):
@@ -438,6 +596,7 @@ def _setup_registry(self):
438
596
self .addCleanup (self .registry .leave_test_mode )
439
597
440
598
def setUp (self ):
599
+ """:meta private: exclude from online docs."""
441
600
super (IntegrityCase , self ).setUp ()
442
601
443
602
def commit (self ):
0 commit comments