Browse Source

[REF] module_auto_update: Step 2, add new API

This code comes from the module_checksum_upgrade proposal
at https://github.com/OCA/server-tools/pull/1176.

* [ADD] module_checksum_upgrade

It provides the core mechanism of module_auto_update without
the cron nor any change to the standard upgrade mechanism.
Instead it provides an API on which module_auto_update can build,
as well as a method which can be called from a script to run
the upgrade of modules for which the checksum has changed.

* [IMP] refactor module_auto_update

Make it depend on module_checksum_upgrade which provides
the core mechanisms of managing the checksums. module_auto_update
makes it automatic.

* [IMP] module_checksum_upgrade: better exclusion mechanism

Ignore files based on exclude patterns.
Ignore uninstalled languages.
Better default for patterns to ignore (*.pyc,*.pyo,*.pot,static/*)

For better control on the hashing mechanism implement our own:
it's quite easy, and the checksumdir module used previously had
no test.

* [MIG] module_auto_update: adapt to new checksum mechanism

* [IMP] module_checksum_upgrade: raise in case of
 incomplete upgrade

* [IMP] module_checksum_upgrade: improve default exclusion
 pattern

* [IMP] module_checksum_upgrade: control translations
 overwrite

* [IMP] module_checksum_upgrade: one more test

* [IMP] module_checksum_upgrade: credits [ci skip]
pull/1217/head
Stéphane Bidoul (ACSONE) 7 years ago
committed by Benjamin Willig
parent
commit
b08ea7e0a2
  1. 43
      module_auto_update/README.rst
  2. 6
      module_auto_update/__manifest__.py
  3. 45
      module_auto_update/addon_hash.py
  4. 6
      module_auto_update/hooks.py
  5. 1
      module_auto_update/models/__init__.py
  6. 148
      module_auto_update/models/module.py
  7. 44
      module_auto_update/models/module_deprecated.py
  8. 2
      module_auto_update/tests/__init__.py
  9. 1
      module_auto_update/tests/sample_module/README.rst
  10. 1
      module_auto_update/tests/sample_module/data/f1.xml
  11. 1
      module_auto_update/tests/sample_module/data/f2.xml
  12. 1
      module_auto_update/tests/sample_module/i18n/en.po
  13. 1
      module_auto_update/tests/sample_module/i18n/en_US.po
  14. 1
      module_auto_update/tests/sample_module/i18n/fr.po
  15. 1
      module_auto_update/tests/sample_module/i18n/fr_BE.po
  16. 1
      module_auto_update/tests/sample_module/i18n/test.pot
  17. 1
      module_auto_update/tests/sample_module/i18n_extra/en.po
  18. 1
      module_auto_update/tests/sample_module/i18n_extra/fr.po
  19. 1
      module_auto_update/tests/sample_module/i18n_extra/nl_NL.po
  20. 1
      module_auto_update/tests/sample_module/models/stuff.py
  21. BIN
      module_auto_update/tests/sample_module/models/stuff.pyc
  22. 1
      module_auto_update/tests/sample_module/static/src/some.js
  23. 67
      module_auto_update/tests/test_addon_hash.py
  24. 203
      module_auto_update/tests/test_module.py
  25. 15
      module_auto_update/tests/test_module_deprecated.py

43
module_auto_update/README.rst

@ -6,31 +6,43 @@
Module Auto Update Module Auto Update
================== ==================
This module will automatically check for and apply module upgrades on a schedule.
Upgrade checking is accomplished by comparing the SHA1 checksums of currently-installed modules to the checksums of corresponding modules in the addons directories.
Installation
============
Prior to installing this module, you need to:
#. Install checksumdir with ``pip install checksumdir``
#. Ensure all installed modules are up-to-date. When installed, this module will assume the versions found in the addons directories are currently installed.
This addon provides mechanisms to compute sha1 hashes of installed addons,
and save them in the database. It also provides a method that exploits these
mechanisms to update a database by upgrading only the modules for which the
hash has changed since the last successful upgrade.
Configuration Configuration
============= =============
The default time for checking and applying upgrades is 3:00 AM (UTC). To change this schedule, modify the "Perform Module Upgrades" scheduled action.
This module supports the following system parameters:
This module will ignore ``.pyc`` and ``.pyo`` file extensions by default. To modify this, create a ``module_auto_update.checksum_excluded_extensions`` system parameter with the desired extensions listed as comma-separated values.
* ``module_auto_update.exclude_patterns``: comma-separated list of file
name patterns to ignore when computing addon checksums. Defaults to
``*.pyc,*.pyo,i18n/*.pot,i18n_extra/*.pot,static/*``.
Filename patterns must be compatible with the python ``fnmatch`` function.
In addition to the above pattern, .po files corresponding to languages that
are not installed in the Odoo database are ignored when computing checksums.
Usage Usage
===== =====
Modules scheduled for upgrade can be viewed by clicking the "Updates" menu item in the Apps sidebar.
The main method provided by this module is ``upgrade_changed_checksum``
on ``ir.module.module``. It runs a database upgrade for all installed
modules for which the hash has changed since the last successful
run of this method. On success it saves the hashes in the database.
The first time this method is invoked after installing the module, it
runs an upgrade of all modules, because it has not saved the hashes yet.
This is by design, priviledging safety. Should this be an issue,
the method ``_save_installed_checksums`` can be invoked in a situation
where one is sure all modules on disk are installed and up-to-date in the
database.
An easy way to invoke this upgrade mechanism is by issuing the following
in an Odoo shell session::
To perform upgrades manually, click the "Apply Scheduled Upgrades" menu item in the Apps sidebar.
env['ir.module.module'].upgrade_changed_checksum()
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
@ -58,6 +70,7 @@ Contributors
* Brent Hughes <brent.hughes@laslabs.com> * Brent Hughes <brent.hughes@laslabs.com>
* Juan José Scarafía <jjs@adhoc.com.ar> * Juan José Scarafía <jjs@adhoc.com.ar>
* Jairo Llopis <jairo.llopis@tecnativa.com> * Jairo Llopis <jairo.llopis@tecnativa.com>
* Stéphane Bidoul <stephane.bidoul@acsone.eu> (https://acsone.eu)
Do not contact contributors directly about support or help with technical issues. Do not contact contributors directly about support or help with technical issues.

6
module_auto_update/__manifest__.py

@ -10,16 +10,12 @@
'author': 'LasLabs, ' 'author': 'LasLabs, '
'Juan José Scarafía, ' 'Juan José Scarafía, '
'Tecnativa, ' 'Tecnativa, '
'ACSONE SA/NV, '
'Odoo Community Association (OCA)', 'Odoo Community Association (OCA)',
'license': 'LGPL-3', 'license': 'LGPL-3',
'application': False, 'application': False,
'installable': True, 'installable': True,
'post_init_hook': 'post_init_hook', 'post_init_hook': 'post_init_hook',
'external_dependencies': {
'python': [
'checksumdir',
],
},
'depends': [ 'depends': [
'base', 'base',
], ],

45
module_auto_update/addon_hash.py

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright 2018 ACSONE SA/NV.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from fnmatch import fnmatch
import hashlib
import os
def _fnmatch(filename, patterns):
for pattern in patterns:
if fnmatch(filename, pattern):
return True
return False
def _walk(top, exclude_patterns, keep_langs):
keep_langs = {l.split('_')[0] for l in keep_langs}
for dirpath, dirnames, filenames in os.walk(top):
dirnames.sort()
reldir = os.path.relpath(dirpath, top)
if reldir == '.':
reldir = ''
for filename in sorted(filenames):
filepath = os.path.join(reldir, filename)
if _fnmatch(filepath, exclude_patterns):
continue
if keep_langs and reldir in {'i18n', 'i18n_extra'}:
basename, ext = os.path.splitext(filename)
if ext == '.po':
if basename.split('_')[0] not in keep_langs:
continue
yield filepath
def addon_hash(top, exclude_patterns, keep_langs):
"""Compute a sha1 digest of file contents."""
m = hashlib.sha1()
for filepath in _walk(top, exclude_patterns, keep_langs):
# hash filename so empty files influence the hash
m.update(filepath.encode('utf-8'))
# hash file content
with open(os.path.join(top, filepath), 'rb') as f:
m.update(f.read())
return m.hexdigest()

6
module_auto_update/hooks.py

@ -6,8 +6,4 @@ from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry): def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {}) env = api.Environment(cr, SUPERUSER_ID, {})
installed_modules = env['ir.module.module'].search([
('state', '=', 'installed'),
])
for r in installed_modules:
r.checksum_installed = r.checksum_dir
env['ir.module.module']._save_installed_checksums()

1
module_auto_update/models/__init__.py

@ -1,3 +1,4 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import module
from . import module_deprecated from . import module_deprecated

148
module_auto_update/models/module.py

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# Copyright 2018 ACSONE SA/NV.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import json
import logging
import os
from openerp import api, exceptions, models, tools
from openerp.modules.module import get_module_path
from ..addon_hash import addon_hash
PARAM_INSTALLED_CHECKSUMS = \
'module_auto_update.installed_checksums'
PARAM_EXCLUDE_PATTERNS = \
'module_auto_update.exclude_patterns'
DEFAULT_EXCLUDE_PATTERNS = \
'*.pyc,*.pyo,i18n/*.pot,i18n_extra/*.pot,static/*'
_logger = logging.getLogger(__name__)
class IncompleteUpgradeError(exceptions.UserError):
pass
class Module(models.Model):
_inherit = 'ir.module.module'
@api.multi
def _get_checksum_dir(self):
self.ensure_one()
exclude_patterns = self.env["ir.config_parameter"].get_param(
PARAM_EXCLUDE_PATTERNS,
DEFAULT_EXCLUDE_PATTERNS,
)
exclude_patterns = [p.strip() for p in exclude_patterns.split(',')]
keep_langs = self.env['res.lang'].search([]).mapped('code')
module_path = get_module_path(self.name)
if module_path and os.path.isdir(module_path):
checksum_dir = addon_hash(
module_path,
exclude_patterns,
keep_langs,
)
else:
checksum_dir = False
return checksum_dir
@api.model
def _get_saved_checksums(self):
Icp = self.env['ir.config_parameter']
return json.loads(Icp.get_param(PARAM_INSTALLED_CHECKSUMS, '{}'))
@api.model
def _save_checksums(self, checksums):
Icp = self.env['ir.config_parameter']
Icp.set_param(PARAM_INSTALLED_CHECKSUMS, json.dumps(checksums))
@api.model
def _save_installed_checksums(self):
checksums = {}
installed_modules = self.search([('state', '=', 'installed')])
for module in installed_modules:
checksums[module.name] = module._get_checksum_dir()
self._save_checksums(checksums)
@api.model
def _get_modules_partially_installed(self):
return self.search([
('state', 'in', ('to install', 'to remove', 'to upgrade')),
])
@api.model
def _get_modules_with_changed_checksum(self):
saved_checksums = self._get_saved_checksums()
installed_modules = self.search([('state', '=', 'installed')])
return installed_modules.filtered(
lambda r: r._get_checksum_dir() != saved_checksums.get(r.name),
)
@api.model
def upgrade_changed_checksum(self, overwrite_existing_translations=False):
"""Run an upgrade of the database, upgrading only changed modules.
Installed modules for which the checksum has changed since the
last successful run of this method are marked "to upgrade",
then the normal Odoo scheduled upgrade process
is launched.
If there is no module with a changed checksum, and no module in state
other than installed, uninstalled, uninstallable, this method does
nothing, otherwise the normal Odoo upgrade process is launched.
After a successful upgrade, the checksums of installed modules are
saved.
In case of error during the upgrade, an exception is raised.
If any module remains to upgrade or to uninstall after the upgrade
process, an exception is raised as well.
Note: this method commits the current transaction at each important
step, it is therefore not intended to be run as part of a
larger transaction.
"""
_logger.info(
"Checksum upgrade starting (i18n-overwrite=%s)...",
overwrite_existing_translations
)
tools.config['overwrite_existing_translations'] = \
overwrite_existing_translations
_logger.info("Updating modules list...")
self.update_list()
changed_modules = self._get_modules_with_changed_checksum()
if not changed_modules and not self._get_modules_partially_installed():
_logger.info("No checksum change detected in installed modules "
"and all modules installed, nothing to do.")
return
_logger.info("Marking the following modules to upgrade, "
"for their checksums changed: %s...",
','.join(changed_modules.mapped('name')))
changed_modules.button_upgrade()
self.env.cr.commit() # pylint: disable=invalid-commit
_logger.info("Upgrading...")
self.env['base.module.upgrade'].upgrade_module()
self.env.cr.commit() # pylint: disable=invalid-commit
_logger.info("Upgrade successful, updating checksums...")
self._save_installed_checksums()
self.env.cr.commit() # pylint: disable=invalid-commit
partial_modules = self._get_modules_partially_installed()
if partial_modules:
raise IncompleteUpgradeError(
"Checksum upgrade successful "
"but incomplete for the following modules: %s" %
','.join(partial_modules.mapped('name'))
)
_logger.info("Checksum upgrade complete.")

44
module_auto_update/models/module_deprecated.py

@ -1,16 +1,7 @@
# Copyright 2017 LasLabs Inc. # Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
from odoo import api, fields, models from odoo import api, fields, models
from odoo.modules.module import get_module_path
_logger = logging.getLogger(__name__)
try:
from checksumdir import dirhash
except ImportError:
_logger.debug('Cannot `import checksumdir`.')
class Module(models.Model): class Module(models.Model):
@ -19,26 +10,27 @@ class Module(models.Model):
checksum_dir = fields.Char( checksum_dir = fields.Char(
compute='_compute_checksum_dir', compute='_compute_checksum_dir',
) )
checksum_installed = fields.Char()
checksum_installed = fields.Char(
compute='_compute_checksum_installed',
inverse='_inverse_checksum_installed',
store=False,
)
@api.depends('name') @api.depends('name')
def _compute_checksum_dir(self): def _compute_checksum_dir(self):
exclude = self.env["ir.config_parameter"].get_param(
"module_auto_update.checksum_excluded_extensions",
"pyc,pyo",
).split(",")
for r in self:
try:
r.checksum_dir = dirhash(
get_module_path(r.name),
'sha1',
excluded_extensions=exclude,
)
except TypeError:
_logger.debug(
"Cannot compute dir hash for %s, module not found",
r.display_name)
for rec in self:
rec.checksum_dir = rec._get_checksum_dir()
def _compute_checksum_installed(self):
saved_checksums = self._get_saved_checksums()
for rec in self:
rec.checksum_installed = saved_checksums.get(rec.name, False)
def _inverse_checksum_installed(self):
saved_checksums = self._get_saved_checksums()
for rec in self:
saved_checksums[rec.name] = rec.checksum_installed
self._save_installed_checksums()
@api.multi @api.multi
def _store_checksum_installed(self, vals): def _store_checksum_installed(self, vals):

2
module_auto_update/tests/__init__.py

@ -1,4 +1,6 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import test_addon_hash
from . import test_module
from . import test_module_deprecated from . import test_module_deprecated
from . import test_module_upgrade_deprecated from . import test_module_upgrade_deprecated

1
module_auto_update/tests/sample_module/README.rst

@ -0,0 +1 @@
Test data for addon_hash module.

1
module_auto_update/tests/sample_module/data/f1.xml

@ -0,0 +1 @@
<odoo/>

1
module_auto_update/tests/sample_module/data/f2.xml

@ -0,0 +1 @@
<odoo/>

1
module_auto_update/tests/sample_module/i18n/en.po

@ -0,0 +1 @@
en text

1
module_auto_update/tests/sample_module/i18n/en_US.po

@ -0,0 +1 @@
en_US

1
module_auto_update/tests/sample_module/i18n/fr.po

@ -0,0 +1 @@
fr

1
module_auto_update/tests/sample_module/i18n/fr_BE.po

@ -0,0 +1 @@
fr_BE

1
module_auto_update/tests/sample_module/i18n/test.pot

@ -0,0 +1 @@
...

1
module_auto_update/tests/sample_module/i18n_extra/en.po

@ -0,0 +1 @@
en

1
module_auto_update/tests/sample_module/i18n_extra/fr.po

@ -0,0 +1 @@
fr

1
module_auto_update/tests/sample_module/i18n_extra/nl_NL.po

@ -0,0 +1 @@
nl_NL

1
module_auto_update/tests/sample_module/models/stuff.py

@ -0,0 +1 @@
1+1

BIN
module_auto_update/tests/sample_module/models/stuff.pyc

1
module_auto_update/tests/sample_module/static/src/some.js

@ -0,0 +1 @@
/* javascript */

67
module_auto_update/tests/test_addon_hash.py

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright 2018 ACSONE SA/NV.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import os
import unittest
from .. import addon_hash
from ..models.module import DEFAULT_EXCLUDE_PATTERNS
class TestAddonHash(unittest.TestCase):
def setUp(self):
super(TestAddonHash, self).setUp()
self.sample_dir = os.path.join(
os.path.dirname(__file__),
'sample_module',
)
def test_basic(self):
files = list(addon_hash._walk(
self.sample_dir,
exclude_patterns=[],
keep_langs=[],
))
self.assertEqual(files, [
'README.rst',
'data/f1.xml',
'data/f2.xml',
'i18n/en.po',
'i18n/en_US.po',
'i18n/fr.po',
'i18n/fr_BE.po',
'i18n/test.pot',
'i18n_extra/en.po',
'i18n_extra/fr.po',
'i18n_extra/nl_NL.po',
'models/stuff.py',
'models/stuff.pyc',
'static/src/some.js',
])
def test_exclude(self):
files = list(addon_hash._walk(
self.sample_dir,
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS.split(','),
keep_langs=['fr_FR', 'nl'],
))
self.assertEqual(files, [
'README.rst',
'data/f1.xml',
'data/f2.xml',
'i18n/fr.po',
'i18n/fr_BE.po',
'i18n_extra/fr.po',
'i18n_extra/nl_NL.po',
'models/stuff.py',
])
def test2(self):
checksum = addon_hash.addon_hash(
self.sample_dir,
exclude_patterns=['*.pyc', '*.pyo', '*.pot', 'static/*'],
keep_langs=['fr_FR', 'nl'],
)
self.assertEqual(checksum, 'fecb89486c8a29d1f760cbd01c1950f6e8421b14')

203
module_auto_update/tests/test_module.py

@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# Copyright 2018 ACSONE SA/NV.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import os
import tempfile
import mock
from openerp.modules import get_module_path
from openerp.tests import common
from openerp.tests.common import TransactionCase
from ..addon_hash import addon_hash
from ..models.module import IncompleteUpgradeError, DEFAULT_EXCLUDE_PATTERNS
MODULE_NAME = 'module_auto_update'
class TestModule(TransactionCase):
def setUp(self):
super(TestModule, self).setUp()
self.own_module = self.env['ir.module.module'].search([
('name', '=', MODULE_NAME),
])
self.own_dir_path = get_module_path(MODULE_NAME)
keep_langs = self.env['res.lang'].search([]).mapped('code')
self.own_checksum = addon_hash(
self.own_dir_path,
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS.split(','),
keep_langs=keep_langs,
)
self.own_writeable = os.access(self.own_dir_path, os.W_OK)
def test_compute_checksum_dir(self):
"""It should compute the directory's SHA-1 hash"""
self.assertEqual(
self.own_module._get_checksum_dir(), self.own_checksum,
'Module directory checksum not computed properly',
)
def test_compute_checksum_dir_ignore_excluded(self):
"""It should exclude .pyc/.pyo extensions from checksum
calculations"""
if not self.own_writeable:
self.skipTest("Own directory not writeable")
with tempfile.NamedTemporaryFile(suffix='.pyc', dir=self.own_dir_path):
self.assertEqual(
self.own_module._get_checksum_dir(), self.own_checksum,
'SHA1 checksum does not ignore excluded extensions',
)
def test_compute_checksum_dir_recomputes_when_file_added(self):
"""It should return a different value when a non-.pyc/.pyo file is
added to the module directory"""
if not self.own_writeable:
self.skipTest("Own directory not writeable")
with tempfile.NamedTemporaryFile(suffix='.py', dir=self.own_dir_path):
self.assertNotEqual(
self.own_module._get_checksum_dir(), self.own_checksum,
'SHA1 checksum not recomputed',
)
def test_saved_checksums(self):
Imm = self.env['ir.module.module']
base_module = Imm.search([('name', '=', 'base')])
self.assertEqual(base_module.state, 'installed')
self.assertFalse(Imm._get_saved_checksums())
Imm._save_installed_checksums()
saved_checksums = Imm._get_saved_checksums()
self.assertTrue(saved_checksums)
self.assertTrue(saved_checksums['base'])
def test_get_modules_with_changed_checksum(self):
Imm = self.env['ir.module.module']
self.assertTrue(Imm._get_modules_with_changed_checksum())
Imm._save_installed_checksums()
self.assertFalse(Imm._get_modules_with_changed_checksum())
@common.at_install(False)
@common.post_install(True)
class TestModuleAfterInstall(TransactionCase):
def setUp(self):
super(TestModuleAfterInstall, self).setUp()
Imm = self.env['ir.module.module']
self.own_module = Imm.search([('name', '=', MODULE_NAME)])
self.base_module = Imm.search([('name', '=', 'base')])
def test_get_modules_partially_installed(self):
Imm = self.env['ir.module.module']
self.assertTrue(
self.own_module not in Imm._get_modules_partially_installed())
self.own_module.button_upgrade()
self.assertTrue(
self.own_module in Imm._get_modules_partially_installed())
self.own_module.button_upgrade_cancel()
self.assertTrue(
self.own_module not in Imm._get_modules_partially_installed())
def test_upgrade_changed_checksum(self):
Imm = self.env['ir.module.module']
Bmu = self.env['base.module.upgrade']
# check modules are in installed state
installed_modules = Imm.search([('state', '=', 'installed')])
self.assertTrue(self.own_module in installed_modules)
self.assertTrue(self.base_module in installed_modules)
self.assertTrue(len(installed_modules) > 2)
# change the checksum of 'base'
Imm._save_installed_checksums()
saved_checksums = Imm._get_saved_checksums()
saved_checksums['base'] = False
Imm._save_checksums(saved_checksums)
changed_modules = Imm._get_modules_with_changed_checksum()
self.assertEqual(len(changed_modules), 1)
self.assertTrue(self.base_module in changed_modules)
def upgrade_module_mock(self_model):
upgrade_module_mock.call_count += 1
# since we are upgrading base, all installed module
# must have been marked to upgrade at this stage
self.assertEqual(self.base_module.state, 'to upgrade')
self.assertEqual(self.own_module.state, 'to upgrade')
installed_modules.write({'state': 'installed'})
upgrade_module_mock.call_count = 0
# upgrade_changed_checksum commits, so mock that
with mock.patch.object(self.env.cr, 'commit'):
# we simulate an install by setting module states
Bmu._patch_method('upgrade_module', upgrade_module_mock)
try:
Imm.upgrade_changed_checksum()
self.assertEqual(upgrade_module_mock.call_count, 1)
self.assertEqual(self.base_module.state, 'installed')
self.assertEqual(self.own_module.state, 'installed')
saved_checksums = Imm._get_saved_checksums()
self.assertTrue(saved_checksums['base'])
self.assertTrue(saved_checksums[MODULE_NAME])
finally:
Bmu._revert_method('upgrade_module')
def test_incomplete_upgrade(self):
Imm = self.env['ir.module.module']
Bmu = self.env['base.module.upgrade']
installed_modules = Imm.search([('state', '=', 'installed')])
# change the checksum of 'base'
Imm._save_installed_checksums()
saved_checksums = Imm._get_saved_checksums()
saved_checksums['base'] = False
Imm._save_checksums(saved_checksums)
def upgrade_module_mock(self_model):
upgrade_module_mock.call_count += 1
# since we are upgrading base, all installed module
# must have been marked to upgrade at this stage
self.assertEqual(self.base_module.state, 'to upgrade')
self.assertEqual(self.own_module.state, 'to upgrade')
installed_modules.write({'state': 'installed'})
# simulate partial upgrade
self.own_module.write({'state': 'to upgrade'})
upgrade_module_mock.call_count = 0
# upgrade_changed_checksum commits, so mock that
with mock.patch.object(self.env.cr, 'commit'):
# we simulate an install by setting module states
Bmu._patch_method('upgrade_module', upgrade_module_mock)
try:
with self.assertRaises(IncompleteUpgradeError):
Imm.upgrade_changed_checksum()
self.assertEqual(upgrade_module_mock.call_count, 1)
finally:
Bmu._revert_method('upgrade_module')
def test_nothing_to_upgrade(self):
Imm = self.env['ir.module.module']
Bmu = self.env['base.module.upgrade']
Imm._save_installed_checksums()
def upgrade_module_mock(self_model):
upgrade_module_mock.call_count += 1
upgrade_module_mock.call_count = 0
# upgrade_changed_checksum commits, so mock that
with mock.patch.object(self.env.cr, 'commit'):
# we simulate an install by setting module states
Bmu._patch_method('upgrade_module', upgrade_module_mock)
try:
Imm.upgrade_changed_checksum()
self.assertEqual(upgrade_module_mock.call_count, 0)
finally:
Bmu._revert_method('upgrade_module')

15
module_auto_update/tests/test_module_deprecated.py

@ -1,7 +1,6 @@
# Copyright 2017 LasLabs Inc. # Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
import os import os
import tempfile import tempfile
@ -11,13 +10,10 @@ from odoo.modules import get_module_path
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
from odoo.tools import mute_logger from odoo.tools import mute_logger
from ..addon_hash import addon_hash
from .. import post_init_hook from .. import post_init_hook
_logger = logging.getLogger(__name__)
try:
from checksumdir import dirhash
except ImportError:
_logger.debug('Cannot `import checksumdir`.')
model = 'odoo.addons.module_auto_update.models.module_deprecated' model = 'odoo.addons.module_auto_update.models.module_deprecated'
@ -35,10 +31,11 @@ class TestModule(TransactionCase):
('name', '=', module_name), ('name', '=', module_name),
]) ])
self.own_dir_path = get_module_path(module_name) self.own_dir_path = get_module_path(module_name)
self.own_checksum = dirhash(
keep_langs = self.env['res.lang'].search([]).mapped('code')
self.own_checksum = addon_hash(
self.own_dir_path, self.own_dir_path,
'sha1',
excluded_extensions=['pyc', 'pyo'],
exclude_patterns=['*.pyc', '*.pyo', '*.pot', 'static/*'],
keep_langs=keep_langs,
) )
self.own_writeable = os.access(self.own_dir_path, os.W_OK) self.own_writeable = os.access(self.own_dir_path, os.W_OK)

Loading…
Cancel
Save