[damned-lies] Use new pathlib.Path wherever possible



commit 2f71c9d49b3dffffb91552399d557bfbccaeaf80
Author: Claude Paroz <claude 2xlibre net>
Date:   Sun Mar 25 17:56:40 2018 +0200

    Use new pathlib.Path wherever possible

 stats/doap.py           |    6 +-
 stats/models.py         |  164 +++++++++++++++++++++++-------------------
 stats/tests/tests.py    |   65 +++++++++--------
 stats/utils.py          |  181 +++++++++++++++++++++--------------------------
 vertimus/tests/tests.py |   47 ++++++++-----
 5 files changed, 236 insertions(+), 227 deletions(-)
---
diff --git a/stats/doap.py b/stats/doap.py
index 8147c5d..c0deb12 100644
--- a/stats/doap.py
+++ b/stats/doap.py
@@ -10,7 +10,7 @@ from people.models import Person
 
 class DoapParser(object):
     def __init__(self, doap_path):
-        self.tree = parse(doap_path)
+        self.tree = parse(str(doap_path))
         self.project_root = self.tree.getroot()
         if not self.project_root.tag.endswith("Project"):
             # Try to get Project from root children (root might be <rdf:RDF>)
@@ -51,8 +51,8 @@ class DoapParser(object):
 def update_doap_infos(module):
     """ Should only be called inside an "update-stats" context of a master branch,
         so there is no need for any extra checkout/locking strategy """
-    doap_path = os.path.join(module.get_head_branch().co_path(), "%s.doap" % module.name)
-    if not os.access(doap_path, os.F_OK):
+    doap_path = module.get_head_branch().co_path() / ("%s.doap" % module.name)
+    if not doap_path.exists():
         return
     try:
         tree = DoapParser(doap_path)
diff --git a/stats/models.py b/stats/models.py
index d8ffe8f..4ece894 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -1,4 +1,3 @@
-import fnmatch
 import logging
 import os, sys, re
 import shutil
@@ -229,9 +228,9 @@ class Branch(models.Model):
                 shutil.rmtree(self.co_path())
         elif self.module.vcs_type == 'git':
             wdir = self.co_path()
-            if os.access(wdir, os.W_OK):
+            if os.access(str(wdir), os.W_OK):
                 if self.is_head():
-                    shutil.rmtree(wdir)
+                    shutil.rmtree(str(wdir))
                 else:
                     with ModuleLock(self.module):
                         utils.run_shell_command(['git', 'checkout', 'master'], cwd=wdir)
@@ -239,8 +238,8 @@ class Branch(models.Model):
         #To be implemented for hg/bzr
 
         # Remove the pot/po generated files
-        if os.access(self.output_dir('ui'), os.W_OK):
-            shutil.rmtree(self.output_dir('ui'))
+        if os.access(str(self.output_dir('ui')), os.W_OK):
+            shutil.rmtree(str(self.output_dir('ui')))
 
     def __eq__(self, other):
         if not isinstance(other, self.__class__):
@@ -279,11 +278,11 @@ class Branch(models.Model):
         """ This method determine if some file has changed based on its hash
             Always returns true if this is the first time the path is checked
         """
-        full_path = os.path.join(self.co_path(), rel_path)
-        if not os.access(full_path, os.R_OK):
+        full_path = self.co_path() / rel_path
+        if not full_path.exists():
             return False # Raise exception?
         new_hash = utils.compute_md5(full_path)
-        if self.file_hashes.get(rel_path, None) == new_hash:
+        if self.file_hashes and self.file_hashes.get(rel_path, None) == new_hash:
             return False
         else:
             self.file_hashes[rel_path] = new_hash
@@ -335,7 +334,7 @@ class Branch(models.Model):
             branch_dir = self.module.name
         else:
             branch_dir = self.module.name + "." + self.name
-        return os.path.join(settings.SCRATCHDIR, self.module.vcs_type, branch_dir)
+        return Path(settings.SCRATCHDIR, self.module.vcs_type, branch_dir)
 
     def get_domains(self):
         """
@@ -355,14 +354,13 @@ class Branch(models.Model):
         return True
 
     def domain_path(self, domain):
-        return os.path.join(self.co_path(), domain.directory)
+        return self.co_path() / domain.directory
 
     def output_dir(self, dom_type):
         """ Directory where generated pot and po files are written on local system """
         subdir = {'ui': '', 'doc': 'docs'}[dom_type]
-        dirname = os.path.join(settings.POTDIR, self.module.name + "." + self.name, subdir)
-        if not os.path.isdir(dirname):
-            os.makedirs(dirname)
+        dirname = Path(settings.POTDIR, self.module.name + "." + self.name, subdir)
+        dirname.mkdir(parents=True, exist_ok=True)
         return dirname
 
     def get_stats(self, typ, mandatory_langs=[]):
@@ -417,7 +415,7 @@ class Branch(models.Model):
                 # 1. Initial settings
                 # *******************
                 domain_path = self.domain_path(dom)
-                if not os.access(domain_path, os.X_OK):
+                if not domain_path.exists():
                     # Delete existing stats, if any
                     Statistics.objects.filter(branch=self, domain=dom).delete()
                     continue
@@ -462,11 +460,11 @@ class Branch(models.Model):
 
                 # 4. Compare with old pot files, various checks
                 # *****************************
-                previous_pot = os.path.join(self.output_dir(dom.dtype), '%s.%s.pot' % (dom.potbase(), 
self.name))
+                previous_pot = self.output_dir(dom.dtype) / ('%s.%s.pot' % (dom.potbase(), self.name))
                 if not potfile:
                     logging.error("Can't generate POT file for %s/%s." % (
                         self.module.name, dom.directory))
-                    if os.access(previous_pot, os.R_OK):
+                    if previous_pot.exists():
                         # Use old POT file
                         potfile = previous_pot
                         pot_stat.set_error('error', ugettext_noop("Can’t generate POT file, using old one."))
@@ -478,7 +476,7 @@ class Branch(models.Model):
                 # ***********************************
                 changed_status = utils.CHANGED_WITH_ADDITIONS
 
-                if os.access(previous_pot, os.R_OK):
+                if previous_pot.exists():
                     # Compare old and new POT
                     changed_status, diff = utils.pot_diff_status(previous_pot, potfile)
                     if string_frozen and dom.dtype == 'ui' and changed_status == 
utils.CHANGED_WITH_ADDITIONS:
@@ -488,11 +486,14 @@ class Branch(models.Model):
                 # ***********************************
                 pot_stat.update_stats(potfile, pot_method, msgfmt_checks=False)
 
-                if potfile != previous_pot and not utils.copy_file(potfile, previous_pot):
-                    pot_stat.set_error('error', ugettext_noop("Can’t copy new POT file to public location."))
+                if potfile != previous_pot:
+                    try:
+                        shutil.copyfile(str(potfile), str(previous_pot))
+                    except Exception:
+                        pot_stat.set_error('error', ugettext_noop("Can’t copy new POT file to public 
location."))
 
                 # Send pot_has_changed signal
-                if os.access(previous_pot, os.R_OK) and changed_status != utils.NOT_CHANGED:
+                if previous_pot.exists() and changed_status != utils.NOT_CHANGED:
                     signals.pot_has_changed.send(sender=self, potfile=potfile, branch=self, domain=dom)
 
                 # 7. Update language po files and update DB
@@ -501,14 +502,19 @@ class Branch(models.Model):
                 langs_with_ext_errors = [stat.language.locale for stat in stats_with_ext_errors]
                 dom_langs = dom.get_lang_files(self.co_path())
                 for lang, pofile in dom_langs:
-                    outpo = os.path.join(self.output_dir(dom.dtype), dom.potbase() + "." + self.name + "." + 
lang + ".po")
-
-                    if not force and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING) 
and os.access(outpo, os.R_OK) \
-                       and os.stat(pofile)[8] < os.stat(outpo)[8] and not lang in langs_with_ext_errors :
+                    outpo = self.output_dir(dom.dtype) / (
+                        '%s.%s.%s.po' % (dom.potbase(), self.name, lang)
+                    )
+                    if (not force and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
+                            and outpo.exists()
+                            and pofile.stat().st_mtime < outpo.stat().st_mtime
+                            and not lang in langs_with_ext_errors):
                         continue
 
                     # msgmerge po with recent pot
-                    utils.run_shell_command(['msgmerge', '--previous', '-o', outpo, pofile, potfile])
+                    utils.run_shell_command([
+                        'msgmerge', '--previous', '-o', str(outpo), str(pofile), str(potfile)
+                    ])
 
                     # Get Statistics object
                     try:
@@ -541,15 +547,15 @@ class Branch(models.Model):
     def _exists(self):
         """ Determine if branch (self) already exists (i.e. already checked out) on local FS """
         if self.module.vcs_type == 'git':
-            if not os.path.exists(self.co_path()):
+            if not self.co_path().exists():
                 return False
             command = "git branch | grep %s" % self.name
             status, output, errs = utils.run_shell_command(command, cwd=self.co_path())
             return output != ""
         elif self.module.vcs_type == 'hg':
-            return self.id != None and os.access(self.co_path(), os.X_OK | os.W_OK)
+            return self.id != None and os.access(str(self.co_path()), os.X_OK | os.W_OK)
         else:
-            return os.access(self.co_path(), os.X_OK | os.W_OK)
+            return os.access(str(self.co_path()), os.X_OK | os.W_OK)
 
     def checkout(self):
         """ Do a checkout or an update of the VCS files """
@@ -641,28 +647,28 @@ class Branch(models.Model):
             raise NotImplementedError("Commit is not implemented for '%s'" % vcs_type)
 
         locale = language.locale
-        commit_dir = os.path.join(self.co_path(), domain.directory)
+        commit_dir = self.co_path() / domain.directory
         prefix = '' if domain.dtype == 'ui' else locale
         dest_filename = os.path.join(prefix, "%s.po" % locale)
-        dest_path = os.path.join(commit_dir, dest_filename)
+        dest_path = commit_dir / dest_filename
 
         with ModuleLock(self.module):
             self.update_repo()
             if vcs_type == "git":
-                already_exist = os.access(dest_path, os.F_OK)
+                already_exist = dest_path.exists()
                 if not already_exist and domain.dtype != 'ui':
                     raise Exception("Sorry, adding new translations for documentation is not yet supported.")
 
                 # Copy file in repo
-                utils.copy_file(po_file, dest_path)
+                shutil.copyfile(str(po_file), str(dest_path))
 
                 # git add file.po
                 utils.run_shell_command(
                     ['git', 'add', dest_filename], raise_on_error=True, cwd=commit_dir)
                 if not already_exist:
                     # Add locale to LINGUAS
-                    linguas_file = os.path.join(commit_dir, "LINGUAS")
-                    if os.access(linguas_file, os.F_OK):
+                    linguas_file = commit_dir / "LINGUAS"
+                    if linguas_file.exists():
                         utils.insert_locale_in_linguas(linguas_file, locale)
                         utils.run_shell_command(
                             ['git', 'add', 'LINGUAS'], raise_on_error=True, cwd=commit_dir)
@@ -690,8 +696,12 @@ class Branch(models.Model):
 
         # Finish by updating stats
         if already_exist:
-            stat = Statistics.objects.get(language=language, branch=self, domain=domain)
-            stat.update_stats(dest_path)
+            try:
+                stat = Statistics.objects.get(language=language, branch=self, domain=domain)
+            except Statistics.DoesNotExist:
+                self.update_stats(force=False, checkout=False, domain=domain)
+            else:
+                stat.update_stats(dest_path)
         else:
             self.update_stats(force=False, checkout=False, domain=domain)
         return force_text(commit_hash)
@@ -705,7 +715,7 @@ class Branch(models.Model):
             raise NotImplementedError("Commit cherry-pick is not implemented for '%s'" % 
self.module.vcs_type)
         with ModuleLock(self.module):
             self.update_repo()
-            commit_dir = os.path.join(self.co_path(), domain.directory)
+            commit_dir = self.co_path() / domain.directory
             result = utils.run_shell_command(
                 ['git', 'cherry-pick', '-x', commit_hash], cwd=commit_dir)
             if result[0] == utils.STATUS_OK:
@@ -782,26 +792,28 @@ class Domain(models.Model):
                 return "docbook"
 
     def get_lang_files(self, base_path):
-        """ Returns a list of language files on filesystem, as tuple (lang, lang_file) -> lang_file with 
complete path """
+        """
+        Returns a list of language files on filesystem, as a list of tuples:
+        [(lang, lang_file), ...] -> lang_file as Path.
+        """
         flist = []
-        dom_path = os.path.join(base_path, self.directory)
-        for item in os.listdir(dom_path):
-            if item[-3:] == ".po":
-                lang = item[:-3]
-                pofile = os.path.join(dom_path, item)
-                flist.append((lang, pofile))
-            elif os.path.isdir(os.path.join(dom_path, item)):
-                for base_name in [item, self.name.replace("~","/")]:
-                    pofile = os.path.join(dom_path, item, base_name + ".po")
-                    if os.access(pofile, os.F_OK):
-                        flist.append((item, pofile))
+        dom_path = base_path / self.directory
+        for item in dom_path.iterdir():
+            if item.suffix == ".po":
+                lang = item.stem
+                flist.append((lang, item))
+            elif item.is_dir():
+                for base_name in [item.name, self.name.replace("~", "/")]:
+                    pofile = item / (base_name + ".po")
+                    if pofile.exists():
+                        flist.append((item.name, pofile))
                         break
         return flist
 
     def generate_pot_file(self, current_branch):
         """ Return the pot file generated (in the checkout tree), and the error if any """
 
-        vcs_path = os.path.join(current_branch.co_path(), self.directory)
+        vcs_path = current_branch.co_path() / self.directory
         pot_command = self.pot_method
         env = None
         podir = vcs_path
@@ -814,7 +826,7 @@ class Domain(models.Model):
             pot_command = ''
             call_command('update-trans', 'en')
             status, output, errs = 0, '', ''
-            vcs_path = './po'
+            vcs_path = Path('./po')
 
         elif self.pot_method.startswith(('http://', 'https://')):
             # Get POT from URL and save file locally
@@ -823,8 +835,8 @@ class Domain(models.Model):
                 handle = request.urlopen(req)
             except URLError:
                 return "", (("error", ugettext_noop("Error retrieving pot file from URL.")),)
-            potfile = os.path.join(vcs_path, self.potbase() + ".pot")
-            with open(potfile, 'wb') as f:
+            potfile = vcs_path / (self.potbase() + ".pot")
+            with potfile.open('wb') as f:
                 f.write(handle.read())
             pot_command = None
             status = utils.STATUS_OK
@@ -838,19 +850,20 @@ class Domain(models.Model):
         if pot_command:
             status, output, errs = utils.run_shell_command(pot_command, env=env, cwd=podir)
 
-        potfile = os.path.join(vcs_path, self.potbase() + ".pot")
-        if not os.access(potfile, os.R_OK):
+        potfile = vcs_path / (self.potbase() + ".pot")
+        if not potfile.exists():
             # Try to get POT file from command output, with path relative to checkout root
             m = re.search('([\w/-]*\.pot)', output)
             if m:
-                potfile = os.path.join(current_branch.co_path(), m.group(0))
+                potfile = current_branch.co_path() / m.group(0)
             else:
                 # Try to find .pot file in /po dir
-                for file_ in os.listdir(podir):
-                    if fnmatch.fnmatch(file_, '*.pot'):
-                        potfile = os.path.join(podir, file_)
+                for file_ in podir.iterdir():
+                    if file_.match('*.pot'):
+                        potfile = file_
+                        break
 
-        if status != utils.STATUS_OK or not os.access(potfile, os.R_OK):
+        if status != utils.STATUS_OK or not potfile.exists():
             return "", (("error", ugettext_noop("Error regenerating POT file for 
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>")
                                  % {'file': self.potbase(),
                                     'cmd': ' '.join(pot_command) if isinstance(pot_command, list) else 
pot_command,
@@ -862,7 +875,7 @@ class Domain(models.Model):
     def get_xgettext_command(self, branch):
         pot_command = ['xgettext',
                        '--files-from', 'POTFILES.in',
-                       '--directory', branch.co_path(),
+                       '--directory', str(branch.co_path()),
                        '--from-code', 'utf-8',
                        '--add-comments',
                        '--output', '%s.pot' % self.potbase(),
@@ -876,7 +889,7 @@ class Domain(models.Model):
                                             for path in self.extra_its_dirs.split(':')]
             )
         # Parse and use content from: "XGETTEXT_OPTIONS = --keyword=_ --keyword=N_"
-        vcs_path = os.path.join(branch.co_path(), self.directory)
+        vcs_path = branch.co_path() / self.directory
         makefile = utils.MakefileWrapper.find_file([vcs_path], file_name='Makevars')
         if makefile:
             kwds_vars = makefile.read_variable('XGETTEXT_OPTIONS')
@@ -921,25 +934,25 @@ class Domain(models.Model):
         if self.linguas_location:
             # Custom (or no) linguas location
             if self.linguas_location == 'no':
-                return {'langs':None, 'error':''}
+                return {'langs': None, 'error': ''}
             elif self.linguas_location.split('/')[-1] == "LINGUAS":
-                return utils.read_linguas_file(os.path.join(base_path, self.linguas_location))
+                return utils.read_linguas_file(base_path / self.linguas_location)
             else:
                 variable = "ALL_LINGUAS"
                 if "#" in self.linguas_location:
                     file_path, variable = self.linguas_location.split("#")
                 else:
                     file_path = self.linguas_location
-                linguas_file = utils.MakefileWrapper(os.path.join(base_path, file_path))
+                linguas_file = utils.MakefileWrapper(base_path / file_path)
                 langs = linguas_file.read_variable(variable)
                 return {'langs': langs,
                         'error': ugettext_noop("Entry for this language is not present in %(var)s variable 
in %(file)s file." % {
                             'var': variable, 'file': file_path})}
         # Standard linguas location
         if self.dtype == 'ui':
-            return utils.get_ui_linguas(base_path, os.path.join(base_path, self.directory))
+            return utils.get_ui_linguas(base_path, base_path / self.directory)
         elif self.dtype == 'doc':
-            return utils.get_doc_linguas(base_path, os.path.join(base_path, self.directory))
+            return utils.get_doc_linguas(base_path, base_path / self.directory)
         else:
             raise ValueError("Domain dtype should be one of 'ui', 'doc'")
 
@@ -1313,7 +1326,7 @@ class PoFile(models.Model):
             return int(100*self.untranslated_words/pot_size)
 
     def update_stats(self):
-        stats = utils.po_file_stats(self.path, msgfmt_checks=False)
+        stats = utils.po_file_stats(Path(self.path), msgfmt_checks=False)
         self.translated   = stats['translated']
         self.fuzzy        = stats['fuzzy']
         self.untranslated = stats['untranslated']
@@ -1480,7 +1493,8 @@ class Statistics(models.Model):
                 fig2['translated_file'] = False
                 # Check if a translated figure really exists or if the English one is used
                 if (self.language and
-                   os.path.exists(os.path.join(self.branch.co_path(), self.domain.directory, 
self.language.locale, fig['path']))):
+                        (self.branch.co_path() / self.domain.directory / self.language.locale
+                         / fig['path']).exists()):
                     fig2['trans_remote_url'] = url_model % (self.language.locale, fig['path'])
                     fig2['translated_file'] = True
                 figures.append(fig2)
@@ -1540,7 +1554,7 @@ class Statistics(models.Model):
         if not self.full_po:
             self.full_po = PoFile.objects.create(path=file_path)
             self.save()
-        self.full_po.path = file_path
+        self.full_po.path = str(file_path)
         self.full_po.translated = int(stats['translated'])
         self.full_po.fuzzy = int(stats['fuzzy'])
         self.full_po.untranslated = int(stats['untranslated'])
@@ -1565,22 +1579,22 @@ class Statistics(models.Model):
             if (self.full_po.fuzzy + self.full_po.untranslated) > 0 and not self.branch.is_archive_only():
                 # Generate partial_po and store partial stats
                 if self.full_po.path.endswith('.pot'):
-                    part_po_path = self.full_po.path[:-3] + "reduced.pot"
+                    part_po_path = Path(self.full_po.path[:-3] + "reduced.pot")
                 else:
-                    part_po_path = self.full_po.path[:-3] + ".reduced.po"
-                utils.po_grep(self.full_po.path, part_po_path, self.domain.red_filter)
+                    part_po_path = Path(self.full_po.path[:-3] + ".reduced.po")
+                utils.po_grep(self.full_po.path, str(part_po_path), self.domain.red_filter)
                 part_stats = utils.po_file_stats(part_po_path, msgfmt_checks=False)
                 if (part_stats['translated'] + part_stats['fuzzy'] + part_stats['untranslated'] ==
                         stats['translated'] + stats['fuzzy'] + stats['untranslated']):
                     # No possible gain, set part_po = full_po so it is possible to compute complete stats at 
database level
                     part_po_equals_full_po()
-                    os.remove(part_po_path)
+                    part_po_path.unlink()
                 else:
                     utils.add_custom_header(part_po_path, "X-DamnedLies-Scope", "partial")
                     if not self.part_po or self.part_po == self.full_po:
                         self.part_po = PoFile.objects.create(path=part_po_path)
                         self.save()
-                    self.part_po.path = part_po_path
+                    self.part_po.path = str(part_po_path)
                     self.part_po.translated = part_stats['translated']
                     self.part_po.fuzzy = part_stats['fuzzy']
                     self.part_po.untranslated = part_stats['untranslated']
diff --git a/stats/tests/tests.py b/stats/tests/tests.py
index dbb1209..d2e594c 100644
--- a/stats/tests/tests.py
+++ b/stats/tests/tests.py
@@ -3,6 +3,7 @@ import os
 import shutil
 import tempfile
 from datetime import date
+from pathlib import Path
 from unittest import skipUnless
 
 from django.conf import settings
@@ -168,15 +169,14 @@ class ModuleTestCase(TestCase):
         checkout_path = self.branch.co_path()
         branch = Branch.objects.create(name="gnome-hello-1-4", module = self.mod)
         branch.delete()
-        self.assertTrue(os.access(checkout_path, os.F_OK))
+        self.assertTrue(checkout_path.exists())
         self.branch.delete()
-        self.assertFalse(os.access(checkout_path, os.F_OK))
+        self.assertFalse(checkout_path.exists())
 
     def test_create_unexisting_branch(self):
         """ Try to create a non-existing branch """
         Branch.checkout_on_creation = True
-        branch = Branch(name="trunk2",
-                        module = self.mod)
+        branch = Branch(name="trunk2", module = self.mod)
         self.assertRaises(ValidationError, branch.clean)
 
     def test_edit_branches_form(self):
@@ -236,12 +236,12 @@ class ModuleTestCase(TestCase):
         self.branch.update_stats(force=False)
 
         # Create a new file with translation
-        new_file_path = os.path.join(self.branch.co_path(), "dummy_file.py")
+        new_file_path = self.branch.co_path() / "dummy_file.py"
         new_string = "Dummy string for D-L tests"
-        with open(new_file_path, 'w') as fh:
+        with new_file_path.open('w') as fh:
             fh.write("a = _('%s')\n" % new_string)
         # Add the new file to POTFILES.in
-        with open(os.path.join(self.branch.co_path(), "po", "POTFILES.in"), 'a') as fh:
+        with (self.branch.co_path() / "po" / "POTFILES.in").open('a') as fh:
             fh.write("dummy_file.py\n")
         # Regenerate stats (mail should be sent)
         self.branch.update_stats(force=False, checkout=False)
@@ -259,7 +259,7 @@ class ModuleTestCase(TestCase):
             pot_method='https://l10n.gnome.org/POT/damned-lies.master/damned-lies.master.pot')
         self.branch.checkout()
         potfile, errs = dom.generate_pot_file(self.branch)
-        self.assertTrue(os.path.exists(potfile))
+        self.assertTrue(potfile.exists())
 
     @test_scratchdir
     def test_dynamic_po(self):
@@ -288,7 +288,7 @@ class ModuleTestCase(TestCase):
     @test_scratchdir
     def test_commit_po(self):
         branch = self.mod.get_head_branch()
-        po_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.po')
+        po_file = Path(__file__).parent / 'test.po'
         domain = self.mod.domain_set.get(name='po')
         fr_lang = Language.objects.get(locale='fr')
         with self.assertRaisesRegex(Exception, 'read-only mode'):
@@ -327,8 +327,7 @@ class ModuleTestCase(TestCase):
             self.assertIn('intltool-update -g', cmds[-1])
 
         # Check lang has been inserted in LINGUAS file, before 'de'
-        with open(os.path.join(branch.co_path(), domain.directory, 'LINGUAS')) as fh:
-            self.assertIn('bem\nde', fh.read())
+        self.assertIn('bem\nde', (branch.domain_path(domain) / 'LINGUAS').read_text())
 
         # Documentation
         domain = self.mod.domain_set.get(name='help')
@@ -360,7 +359,7 @@ class ModuleTestCase(TestCase):
         stat.pk, stat.full_po, stat.part_po = None, None, None
         stat.branch = branch
         stat.save()
-        po_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.po')
+        po_file = Path(__file__).parent / 'test.po'
         self.mod.vcs_root = self.mod.vcs_root.replace('git://', 'ssh://')
         self.mod.save()
 
@@ -536,17 +535,22 @@ class FigureTests(TestCase):
         """ Detect warning if translated figure is identical to original figure """
         branch = Branch.objects.get(module__name='gnome-hello', name='master')
         pot_stat = Statistics.objects.get(branch=branch, domain__name='help', language__isnull=True)
-        fig_path = os.path.join(pot_stat.branch.co_path(), pot_stat.domain.directory, 'C', 
pot_stat.get_figures()[0]['path'])
+        fig_path = str(
+            pot_stat.branch.co_path() / pot_stat.domain.directory / 'C' /
+            pot_stat.get_figures()[0]['path']
+        )
         shutil.copyfile(fig_path, fig_path.replace('/C/', '/fr/'))
         doc_stat = Statistics.objects.get(branch=branch, domain__name='help', language__locale='fr')
-        errs = utils.check_identical_figures(doc_stat.get_figures(), os.path.join(branch.co_path(), 'help'), 
'fr')
+        errs = utils.check_identical_figures(
+            doc_stat.get_figures(), branch.co_path() / 'help', 'fr',
+        )
         self.assertEqual(len(errs), 1)
         self.assertTrue(errs[0][1].startswith("Figures should not be copied"))
 
 
 class UtilsTests(TestCase):
     def test_read_file_variable(self):
-        base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "help_docbook")
+        base_path = Path(__file__).parent / "help_docbook"
         makefile = utils.MakefileWrapper.find_file([base_path])
         var_content = makefile.read_variable("DOC_INCLUDES")
         self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml', '$(NULL)'])
@@ -555,7 +559,7 @@ class UtilsTests(TestCase):
         self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml'])
 
     def test_read_meson_variables(self):
-        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "help_mallard")
+        path = Path(__file__).parent / "help_mallard"
         meson_file = utils.MakefileWrapper.find_file([path], file_name='meson.build')
         self.assertEqual(meson_file.read_variable('yelp.languages'), ['es', 'fr'])
         self.assertEqual(
@@ -563,13 +567,13 @@ class UtilsTests(TestCase):
             ['index.page', 'what-is.page', 'legal.xml']
         )
         # UI meson file
-        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'meson-ui.build')
+        path = Path(__file__).parent / "meson-ui.build"
         meson_file = utils.MesonfileWrapper(path)
         self.assertEqual(meson_file.read_variable('gettext.preset'), 'glib')
         self.assertEqual(meson_file.read_variable('gettext.args'), ['--keyword=Description'])
 
     def test_read_cmake_variables(self):
-        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "help_mallard")
+        path = Path(__file__).parent / "help_mallard"
         cmake_file = utils.MakefileWrapper.find_file([path], file_name='CMakeLists.txt')
         self.assertEqual(
             cmake_file.read_variable('HELP_FILES').split(),
@@ -580,15 +584,14 @@ class UtilsTests(TestCase):
         """
         Test Docbook-style help
         """
-        help_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "help_docbook")
-        self.addCleanup(os.remove, os.path.join(help_path, 'C', "release-notes.pot"))
+        help_path = Path(__file__).parent / "help_docbook"
+        self.addCleanup(os.remove, str(help_path / 'C' / "release-notes.pot"))
         potfile, errs, doc_format = utils.generate_doc_pot_file(help_path, 'release-notes', 'release-notes')
         self.assertEqual(doc_format.tool, 'xml2po')
         self.assertEqual(doc_format.format, 'docbook')
-        pot_path = os.path.join(help_path, "C", "release-notes.pot")
-        self.assertTrue(os.access(pot_path, os.R_OK))
-        with open(pot_path, mode='r') as fh:
-            self.assertIn('#: C/rnlookingforward.xml:11(para)', fh.read())
+        pot_path = help_path / "C" / "release-notes.pot"
+        self.assertTrue(pot_path.exists())
+        self.assertIn('#: C/rnlookingforward.xml:11(para)', pot_path.read_text())
         res = utils.get_fig_stats(pot_path, doc_format=doc_format)
         self.assertEqual(len(res), 1)
         self.assertEqual(res[0]['path'], "figures/rnusers.nautilus.png")
@@ -597,15 +600,14 @@ class UtilsTests(TestCase):
         """
         Test Mallard-style help (with itstool)
         """
-        help_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "help_mallard")
-        self.addCleanup(os.remove, os.path.join(help_path, 'C', 'gnome-hello.pot'))
+        help_path = Path(__file__).parent / "help_mallard"
+        self.addCleanup(os.remove, str(help_path / 'C' / 'gnome-hello.pot'))
         potfile, errs, doc_format = utils.generate_doc_pot_file(help_path, 'gnome-hello', 'gnome-hello')
         self.assertEqual(errs, [])
-        self.assertEqual(potfile, os.path.join(help_path, 'C', 'gnome-hello.pot'))
+        self.assertEqual(potfile, help_path / 'C' / 'gnome-hello.pot')
         self.assertEqual(doc_format.tool, 'itstool')
         self.assertEqual(doc_format.format, 'mallard')
-        with open(potfile, mode='r') as fh:
-            self.assertIn('#: C/what-is.page:7', fh.read())
+        self.assertIn('#: C/what-is.page:7', potfile.read_text())
         res = utils.get_fig_stats(potfile, doc_format=doc_format)
         self.assertEqual(len(res), 1)
         self.assertEqual(res[0]['path'], "figures/gnome-hello-logo.png")
@@ -613,10 +615,9 @@ class UtilsTests(TestCase):
 
     @test_scratchdir
     def test_insert_locale_in_linguas(self):
-        linguas_path = os.path.join(settings.SCRATCHDIR, 'git/gnome-hello/po/LINGUAS')
+        linguas_path = Path(settings.SCRATCHDIR) / 'git' / 'gnome-hello' / 'po' / 'LINGUAS'
         utils.insert_locale_in_linguas(linguas_path, 'xx')
-        with open(linguas_path) as fh:
-            self.assertTrue(fh.read().endswith('xx\n'))
+        self.assertTrue(linguas_path.read_text().endswith('xx\n'))
 
     @skipUnless(utils.has_toolkit, "The Translate Toolkit is required for this test")
     @test_scratchdir
diff --git a/stats/utils.py b/stats/utils.py
index ef9e2ae..8d5350d 100644
--- a/stats/utils.py
+++ b/stats/utils.py
@@ -6,6 +6,7 @@ import re
 import shutil
 import time
 from itertools import islice
+from pathlib import Path
 from subprocess import Popen, PIPE
 from unittest.mock import MagicMock
 
@@ -43,7 +44,7 @@ class DocFormat:
         self.makefile = makefile
         doc_id = makefile.read_variable("HELP_ID", "yelp.project_id")
         uses_itstool = doc_id is not None or isinstance(makefile, MesonfileWrapper)
-        has_page_files = any(f.endswith('.page') for f in self.source_files(force_all=True))
+        has_page_files = any(f.suffix == '.page' for f in self.source_files(force_all=True))
         self.format = 'mallard' if has_page_files else 'docbook'
         self.tool = 'itstool' if uses_itstool else 'xml2po'
 
@@ -54,34 +55,32 @@ class DocFormat:
     def source_files(self, force_all=False):
         """Return help source files, with path relative to help base dir."""
         if force_all:
-            base_path = os.path.dirname(self.makefile.path)
-            source_dir = os.path.join(base_path, 'C')
-            return [os.path.join('C', fname) for fname in os.listdir(source_dir)]
+            base_path = self.makefile.path.parent
+            return [p.relative_to(base_path) for p in (base_path / 'C').iterdir() if p.is_file()]
         else:
             sources = self.makefile.read_variable(self.include_var)
             if not sources:
                 if self.format == "mallard":
                     # fallback to directory listing
                     sources = [
-                        f for f in self.source_files(force_all=True) if f.endswith(".page")
+                        f for f in self.source_files(force_all=True) if f.suffix == ".page"
                     ]
                 if not sources:
                     return []
             if isinstance(sources, str):
                 sources = sources.split()
             return [
-                os.path.join('C', src) for src in sources
-                if src not in ("", "$(NULL)")
+                Path('C', src) for src in sources if src not in ("", "$(NULL)")
             ]
 
     def command(self, potfile, files):
         if self.tool == "itstool":
-            cmd = ['%sitstool' % ITSTOOL_PATH, '-o', potfile]
+            cmd = ['%sitstool' % ITSTOOL_PATH, '-o', str(potfile)]
         elif self.format == "mallard":
-            cmd = ['xml2po', '-m', 'mallard', '-o', potfile, '-e']
+            cmd = ['xml2po', '-m', 'mallard', '-o', str(potfile), '-e']
         else:
-            cmd = ['xml2po', '-o', potfile, '-e']
-        cmd.extend(files)
+            cmd = ['xml2po', '-o', str(potfile), '-e']
+        cmd.extend([str(f) for f in files])
         return cmd
 
     @property
@@ -132,8 +131,8 @@ class MakefileWrapper:
         names = [file_name] if file_name is not None else cls.default_makefiles
         for path in vcs_paths:
             for file_name in names:
-                file_path = os.path.join(path, file_name)
-                if os.access(file_path, os.R_OK):
+                file_path = path / file_name
+                if file_path.exists():
                     if file_name == 'meson.build':
                         return MesonfileWrapper(file_path)
                     elif file_name == 'CMakeLists.txt':
@@ -147,8 +146,7 @@ class MakefileWrapper:
     @cached_property
     def content(self):
         try:
-            with open(self.path, mode='r', encoding='utf-8') as fh:
-                content = fh.read()
+            content = self.path.read_text()
         except IOError:
             return None
         return content
@@ -311,7 +309,7 @@ def ellipsize(val, length):
         val = "%s..." % val[:length]
     return val
 
-def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None, **popen_kwargs):
+def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None, cwd=None, **popen_kwargs):
     logging.debug(cmd)
 
     stdin = None
@@ -319,12 +317,10 @@ def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None, **po
         stdin = PIPE
     if env is not None:
         env = dict(os.environ, **env)
+    if cwd is not None:
+        cwd = str(cwd)  # Popen cwd doesn't support Path yet
     shell = not isinstance(cmd, list)
-    if isinstance(cmd, str):
-        cmd = cmd.encode('utf-8')
-    elif isinstance(cmd, list):
-        cmd = [c.encode('utf-8') for c in cmd]
-    pipe = Popen(cmd, shell=shell, stdin=stdin, stdout=PIPE, stderr=PIPE, env=env, **popen_kwargs)
+    pipe = Popen(cmd, shell=shell, stdin=stdin, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, **popen_kwargs)
     if input_data:
         try:
             pipe.stdin.write(input_data)
@@ -372,28 +368,30 @@ def check_potfiles(po_path):
     errors = []
 
     run_shell_command(['rm', '-f', 'missing', 'notexist'], cwd=po_path)
-    (status, output, errs) = run_shell_command(['intltool-update', '-m'], cwd=po_path)
+    status, output, errs = run_shell_command(['intltool-update', '-m'], cwd=po_path)
 
     if status != STATUS_OK:
         errors.append( ("error", ugettext_noop("Errors while running “intltool-update -m” check.")) )
 
-    missing = os.path.join(po_path, "missing")
-    if os.access(missing, os.R_OK):
-        f = open(missing, "r")
-        errors.append( ("warn",
-                        ugettext_noop("There are some missing files from POTFILES.in: %s")
-                        % ("<ul><li>"
-                        + "</li>\n<li>".join(f.readlines())
-                        + "</li>\n</ul>")) )
-
-    notexist = os.path.join(po_path, "notexist")
-    if os.access(notexist, os.R_OK):
-        f = open(notexist, "r")
-        errors.append(("error",
-                       ugettext_noop("Following files are referenced in either POTFILES.in or POTFILES.skip, 
yet they don’t exist: %s")
-                       % ("<ul><li>"
-                       + "</li>\n<li>".join(f.readlines())
-                       + "</li>\n</ul>")))
+    missing = po_path / "missing"
+    if missing.exists():
+        with missing.open("r") as fh:
+            errors.append(
+                ("warn",
+                ugettext_noop("There are some missing files from POTFILES.in: %s") % (
+                    "<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
+                )
+            )
+
+    notexist = po_path / "notexist"
+    if notexist.exists():
+        with notexist.open("r") as fh:
+            errors.append(
+                ("error",
+                ugettext_noop("Following files are referenced in either POTFILES.in or POTFILES.skip, yet 
they don’t exist: %s") % (
+                    "<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
+                )
+            )
     return errors
 
 def generate_doc_pot_file(vcs_path, potbase, moduleid):
@@ -414,8 +412,8 @@ def generate_doc_pot_file(vcs_path, potbase, moduleid):
         if not modulename:
             modulename = moduleid
         for index_page in ("index.docbook", modulename + ".xml", moduleid + ".xml"):
-            if os.access(os.path.join(vcs_path, "C", index_page), os.R_OK):
-                files.append(os.path.join("C", index_page))
+            if (vcs_path / "C" / index_page).exists():
+                files.append(Path("C", index_page))
                 break
         if not files:
             # Last try: only one xml file in C/...
@@ -427,7 +425,7 @@ def generate_doc_pot_file(vcs_path, potbase, moduleid):
                 return "", errors, doc_format
 
     files.extend(doc_format.source_files())
-    potfile = os.path.join(vcs_path, "C", potbase + ".pot")
+    potfile = vcs_path / "C" / (potbase + ".pot")
     command = doc_format.command(potfile, files)
     status, output, errs = run_shell_command(command, cwd=vcs_path)
 
@@ -440,7 +438,7 @@ def generate_doc_pot_file(vcs_path, potbase, moduleid):
                      )
         potfile = ""
 
-    if not os.access(potfile, os.R_OK):
+    if not potfile.exists():
         return "", errors, doc_format
     else:
         return potfile, errors, doc_format
@@ -452,7 +450,7 @@ def pot_diff_status(pota, potb):
     if int(output) <= 4:
         return NOT_CHANGED, ""
 
-    result_all, result_add_only = potdiff.diff(pota, potb)
+    result_all, result_add_only = potdiff.diff(str(pota), str(potb))
     if not len(result_all) and not len(result_add_only):
         return CHANGED_ONLY_FORMATTING, ""
     elif len(result_add_only):
@@ -473,14 +471,15 @@ def po_file_stats(pofile, msgfmt_checks=True):
         }
     c_env = {"LC_ALL": "C", "LANG": "C", "LANGUAGE": "C"}
 
-    if isinstance(pofile, str):
+    if isinstance(pofile, Path):
         # pofile is a filesystem path
-        filename = os.path.basename(pofile)
-        if not os.access(pofile, os.R_OK):
-            res['errors'].append(("error", ugettext_noop("PO file “%s” does not exist or cannot be read.") % 
pofile))
+        if not pofile.exists():
+            res['errors'].append(
+                ("error", ugettext_noop("PO file “%s” does not exist or cannot be read.") % pofile.name)
+            )
             return res
         input_data = None
-        input_file = pofile
+        input_file = str(pofile)
 
         if has_toolkit:
             status = pocount.calcstats_old(pofile)
@@ -490,7 +489,6 @@ def po_file_stats(pofile, msgfmt_checks=True):
                 res['untranslated_words'] = status['untranslatedsourcewords']
 
     elif isinstance(pofile, File):
-        filename = pofile.name
         input_data = pofile.read()
         input_file = "-"
     else:
@@ -505,11 +503,11 @@ def po_file_stats(pofile, msgfmt_checks=True):
 
     if status != STATUS_OK:
         if msgfmt_checks:
-            res['errors'].append(("error", ugettext_noop("PO file “%s” doesn’t pass msgfmt check: not 
updating.") % (filename)))
+            res['errors'].append(("error", ugettext_noop("PO file “%s” doesn’t pass msgfmt check: not 
updating.") % (pofile.name)))
         else:
-            res['errors'].append(("error", ugettext_noop("Can’t get statistics for POT file “%s”.") % 
(pofile)))
+            res['errors'].append(("error", ugettext_noop("Can’t get statistics for POT file “%s”.") % 
(pofile.name)))
 
-    if msgfmt_checks and input_file != "-" and os.access(pofile, os.X_OK):
+    if msgfmt_checks and input_file != "-" and os.access(str(pofile), os.X_OK):
         res['errors'].append(("warn", ugettext_noop("This PO file has an executable bit set.")))
 
     # msgfmt output stats on stderr
@@ -539,49 +537,50 @@ def po_file_stats(pofile, msgfmt_checks=True):
             (status, output, errs) = run_shell_command(command, env=c_env)
         if status != STATUS_OK:
             res['errors'].append(("warn",
-                              ugettext_noop("PO file “%s” is not UTF-8 encoded.") % (filename)))
+                              ugettext_noop("PO file “%s” is not UTF-8 encoded.") % (pofile.name)))
     return res
 
 def read_linguas_file(full_path):
     """ Read a LINGUAS file (each language code on a line by itself) """
     langs = []
-    lfile = open(full_path, "r")
-    [langs.extend(line.split()) for line in lfile if line[:1]!='#']
-    lfile.close()
-    return {'langs':langs,
-            'error': ugettext_noop("Entry for this language is not present in LINGUAS file.") }
+    with full_path.open("r") as fh:
+        [langs.extend(line.split()) for line in fh if line[:1] != '#']
+    return {
+        'langs': langs,
+        'error': ugettext_noop("Entry for this language is not present in LINGUAS file."),
+    }
 
 def insert_locale_in_linguas(linguas_path, locale):
-    with open(linguas_path, 'r') as fin:
-        with open(linguas_path + "~", 'w') as fout:
-            lang_written = False
-            for line in fin:
-                if not lang_written and line[0] != "#" and line[:5] > locale[:5]:
-                    fout.write(locale + "\n")
-                    lang_written = True
-                fout.write(line)
-            if not lang_written:
+    temp_linguas = linguas_path.parent / (linguas_path.name + "~")
+    with linguas_path.open('r') as fin, temp_linguas.open('w') as fout:
+        lang_written = False
+        for line in fin:
+            if not lang_written and line[0] != "#" and line[:5] > locale[:5]:
                 fout.write(locale + "\n")
-    os.rename(linguas_path + "~", linguas_path)
+                lang_written = True
+            fout.write(line)
+        if not lang_written:
+            fout.write(locale + "\n")
+    temp_linguas.replace(linguas_path)
 
 def get_ui_linguas(module_path, po_path):
     """Get language list in one of po/LINGUAS, configure.ac or configure.in"""
 
-    LINGUAShere = os.path.join(po_path, "LINGUAS")
-    LINGUASpo = os.path.join(module_path, "po", "LINGUAS") # if we're in eg. po-locations/
-    configureac = os.path.join(module_path, "configure.ac")
-    configurein = os.path.join(module_path, "configure.in")
+    LINGUAShere = po_path / "LINGUAS"
+    LINGUASpo = module_path / "po" / "LINGUAS"  # if we're in eg. po-locations/
 
     # is "lang" listed in either of po/LINGUAS, ./configure.ac(ALL_LINGUAS) or ./configure.in(ALL_LINGUAS)
     for LINGUAS in [LINGUAShere, LINGUASpo]:
-        if os.access(LINGUAS, os.R_OK):
+        if LINGUAS.exists():
             return read_linguas_file(LINGUAS)
     # AS_ALL_LINGUAS is a macro that takes all po files by default
-    (status, output, errs) = run_shell_command("grep -qs AS_ALL_LINGUAS %s%sconfigure.*" % (module_path, 
os.sep))
+    status, output, errs = run_shell_command("grep -qs AS_ALL_LINGUAS %s%sconfigure.*" % (module_path, 
os.sep))
     if status == 0:
         return {'langs': None,
                 'error': ugettext_noop("No need to edit LINGUAS file or variable for this module")}
 
+    configureac = module_path / "configure.ac"
+    configurein = module_path / "configure.in"
     for configure in [configureac, configurein]:
         found = MakefileWrapper(configure).read_variable('ALL_LINGUAS')
         if found is not None:
@@ -598,8 +597,8 @@ def get_doc_linguas(module_path, po_path):
         linguas = linguas_file.read_variable("DOC_LINGUAS", "HELP_LINGUAS", "gettext.languages")
     if linguas is None:
         # Try to get LINGUAS file like for UI.
-        LINGUAS_path = os.path.join(po_path, "LINGUAS")
-        if os.access(LINGUAS_path, os.R_OK):
+        LINGUAS_path = po_path / "LINGUAS"
+        if LINGUAS_path.exists():
             return read_linguas_file(LINGUAS_path)
     if linguas is None:
         return {'langs':None,
@@ -627,9 +626,9 @@ def collect_its_data():
         with ModuleLock(branch.module):
             branch.checkout()
             for file_path in files:
-                src = os.path.join(branch.co_path(), file_path)
+                src = branch.co_path() / file_path
                 dest = os.path.join(ITS_DATA_DIR, os.path.basename(file_path))
-                shutil.copyfile(src, dest)
+                shutil.copyfile(str(src), dest)
 
 
 def get_fig_stats(pofile, doc_format):
@@ -666,11 +665,9 @@ def get_fig_stats(pofile, doc_format):
 def check_identical_figures(fig_stats, base_path, lang):
     errors = []
     for fig in fig_stats:
-        trans_path = os.path.join(base_path, lang, fig['path'])
-        if os.access(trans_path, os.R_OK):
-            with open(trans_path, 'rb') as fh:
-                fig_file = fh.read()
-            trans_hash = hashlib.md5(fig_file).hexdigest()
+        trans_path = base_path / lang / fig['path']
+        if trans_path.exists():
+            trans_hash = compute_md5(trans_path)
             if fig['hash'] == trans_hash:
                 errors.append(("warn-ext", "Figures should not be copied when identical to original (%s)." % 
trans_path))
     return errors
@@ -695,25 +692,11 @@ def is_po_reduced(file_path):
     status, output, errs = run_shell_command(['grep', 'X-DamnedLies-Scope: partial', file_path])
     return (status == 0)
 
-def copy_file(file1, file2):
-    try:
-        with open(file1, "rb") as fin, open(file2, "wb") as fout:
-            block = fin.read(16*1024)
-            while block:
-                fout.write(block)
-                block = fin.read(16*1024)
-
-        if os.access(file2, os.R_OK):
-            return 1
-        else:
-            return 0
-    except:
-        return 0
 
 def compute_md5(full_path):
     m = hashlib.md5()
     block_size=2**13
-    with open(full_path, 'rb') as fh:
+    with full_path.open('rb') as fh:
         while True:
             data = fh.read(block_size)
             if not data:
diff --git a/vertimus/tests/tests.py b/vertimus/tests/tests.py
index 8e1b706..5686551 100644
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@ -1,5 +1,6 @@
 import os
 import tempfile
+from pathlib import Path
 from xml.dom.minidom import parseString
 
 from django.conf import settings
@@ -285,8 +286,8 @@ class VertimusTest(TeamsAndRolesTests):
         state = StateTranslating(branch=self.b, domain=self.d, language=self.l, person=self.pt)
         state.save()
 
-        file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "valid_po.po")
-        with open(file_path, 'r') as test_file:
+        file_path = Path(__file__).parent / "valid_po.po"
+        with file_path.open() as test_file:
             action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
             action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
 
@@ -418,9 +419,15 @@ class VertimusTest(TeamsAndRolesTests):
         """
         pers = Person.objects.create(email='user example org', username='username_only')
         Role.objects.create(team=self.t, person=pers, role='reviewer')
-        self.b.module.vcs_root = self.b.module.vcs_root.replace('git://', 'ssh://')
-        self.b.module.save()
-        state = StateProofreading(branch=self.b, domain=self.d, language=self.l, person=pers)
+        m = Module.objects.create(
+            name='gnome-hello', vcs_type='git', vcs_root="ssh://git.gnome.org/gnome-hello",
+        )
+        Branch.checkout_on_creation = False
+        b = Branch(name='master', module=m, file_hashes={})
+        b.save(update_statistics=False)
+        d = Domain.objects.create(module=m, name='po', dtype='ui', directory='po')
+
+        state = StateProofreading(branch=b, domain=d, language=self.l, person=pers)
         state.save()
         # Adding two comments from the commit author, as this might trigger a form error
         action = Action.new_by_name('WC', person=self.pcoo)
@@ -441,11 +448,15 @@ class VertimusTest(TeamsAndRolesTests):
         post_data['author'] = str(self.pcoo.pk)
         form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
         self.assertTrue(form.is_valid())
-        with patch_shell_command() as cmds:
+        # path needed when copying file to commit
+        (self.b.co_path() / 'po').mkdir(parents=True, exist_ok=True)
+        with patch_shell_command(only=['git ']) as cmds:
             action = Action.new_by_name('CI', person=self.pcoo)
             msg = action.apply_on(state, form.cleaned_data)
-        self.assertIn("git commit", cmds[-3])
-        self.assertIn('--author John Coordinator <jcoo imthebigboss fr>', cmds[-3])
+        self.assertIn(
+            'git commit -m Update French translation --author John Coordinator <jcoo imthebigboss fr>',
+            cmds
+        )
         self.assertEqual(msg, 'The file has been successfully committed to the repository.')
         state.refresh_from_db()
         # All actions should have been archived
@@ -460,8 +471,8 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertEqual(len(mail.outbox), 1)  # Mail sent to mailing list
         mail.outbox = []
 
-        file_path = os.path.join(settings.MEDIA_ROOT, action_file.name)
-        self.assertTrue(os.access(file_path, os.W_OK))
+        file_path = Path(settings.MEDIA_ROOT, action_file.name)
+        self.assertTrue(file_path.exists())
 
         action = Action.new_by_name('TC', person=self.pc)
         action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ''})
@@ -480,13 +491,13 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertTrue('Commité' in mail.outbox[0].body or "Committed" in mail.outbox[0].body)
 
         self.assertIsInstance(state, StateNone)
-        self.assertTrue(not os.access(file_path, os.F_OK), "%s not deleted" % file_path)
+        self.assertFalse(file_path.exists(), "%s not deleted" % file_path)
 
         # Remove test file
         action_archived = ActionArchived.objects.get(comment="Done.")
-        filename_archived = os.path.join(settings.MEDIA_ROOT, action_archived.file.name)
+        filename_archived = Path(settings.MEDIA_ROOT, action_archived.file.name)
         action_archived.delete()
-        self.assertTrue(not os.access(filename_archived, os.F_OK), "%s not deleted" % filename_archived)
+        self.assertFalse(filename_archived.exists(), "%s not deleted" % filename_archived)
 
     def test_action_tr(self):
         state = StateTranslated(branch=self.b, domain=self.d, language=self.l)
@@ -642,7 +653,7 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertTrue('file' in form.errors)
 
         # Test a valid po file
-        with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "valid_po.po"), 'rb') as fh:
+        with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
             post_file = MultiValueDict({'file': [File(fh)]})
             form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
             self.assertTrue(form.is_valid())
@@ -689,17 +700,17 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertContains(response, self.m.description)
 
     def test_check_po_file(self):
-        valid_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "valid_po.po")
+        valid_path = Path(__file__).parent / "valid_po.po"
         state = StateProofreading(branch=self.b, domain=self.d, language=self.l, person=self.pr)
         state.save()
-        with open(valid_path, 'r') as test_file:
+        with valid_path.open() as test_file:
             action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
             action.state_db = state
             action.file.save(action.file.name, action.file, save=False)
         self.assertEqual(action.check_po_file(), '')
 
-        error_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "xml_error.po")
-        with open(error_path, 'r') as test_file:
+        error_path = Path(__file__).parent / "xml_error.po"
+        with error_path.open() as test_file:
             action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
             action.state_db = state
             action.file.save(action.file.name, action.file, save=False)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]