Skip to contentSkip to conversation

Bugfix, tests, doc update

PATCH New Paul Eggleton 1 participant 4 messages Jul 19, 2018

lore
Share & tools
lore
#0 lore raw

cover Bugfix, tests, doc update

A fix for a bug in the update script, some regression tests (finally!) and a small bit for the README about setting up dependencies using virtualenv.

The following changes since commit 49981aebf6fab9338f2a570acbb9aa67312c8865:

Add site-wide notice support (2018-07-09 13:50:15 +0200)

are available in the Git repository at:

git://git.yoctoproject.org/layerindex-web paule/fixes4
http://git.yoctoproject.org/cgit.cgi//log/?h=paule/fixes4

Paul Eggleton (3):
update: fix handling of moves outside of a layer Add minimal tests for update script
README: add an example virtualenv-based setup

 .gitignore                           |   1 +
 README                               |  37 ++++-
 layerindex/tools/fixup_duplicates.py |  69 ++++++++++
 layerindex/update_layer.py           |  27 ++--
 pytest.ini                           |   4 +
 tests/test_update.py                 | 195 +++++++++++++++++++++++++++
 6 files changed, 322 insertions(+), 11 deletions(-)
 create mode 100644 layerindex/tools/fixup_duplicates.py
 create mode 100644 pytest.ini
 create mode 100644 tests/test_update.py
Signature · 1 line
2.17.1
#1 lore raw

PATCH 1/3 update: fix handling of moves outside of a layer

If a file is moved (renamed) to a path outside of the layer, e.g. another layer within a multi-layer repository, then we need to treat it as a delete. Up until now we were updating the path and continuing, and then the recipe was also picked up as an add in the other layer, leading to duplicate recipe entries. I'd noticed these duplicates before but up until now I'd thought that they were due to another bug we already fixed, apparently not.

In order to remove these erroneous duplicate entries in existing databases I have also added a layerindex/tools/fixup_duplicates.py script. I've also made the -r/--reload option delete them as well.

---
 layerindex/tools/fixup_duplicates.py | 69 ++++++++++++++++++++++++++++
 layerindex/update_layer.py           | 27 +++++++----
 2 files changed, 87 insertions(+), 9 deletions(-)
 create mode 100644 layerindex/tools/fixup_duplicates.py
layerindex/tools/fixup_duplicates.py new file +69 -0
new file mode 100644
index 00000000..5abe2c2d
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+# Fix recipes that were moved out
+#
+# Copyright (C) 2017 Intel Corporation
+# Author: Paul Eggleton <[email protected]>
+#
+# Licensed under the MIT license, see COPYING.MIT for details
+
+
+import sys
+import os
+
+sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
+
+import optparse
+import utils
+import logging
+
+class DryRunRollbackException(Exception):
+    pass
+
+logger = utils.logger_create('LayerIndexFixup')
+
+
+
+def main():
+    parser = optparse.OptionParser(
+        usage = """
+    %prog [options""")
+
+    parser.add_option("-n", "--dry-run",
+            help = "Don't write any data back to the database",
+            action="store_true", dest="dryrun")
+    parser.add_option("-d", "--debug",
+            help = "Enable debug output",
+            action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
+    parser.add_option("-q", "--quiet",
+            help = "Hide all output except error messages",
+            action="store_const", const=logging.ERROR, dest="loglevel")
+
+    options, args = parser.parse_args(sys.argv)
+
+    utils.setup_django()
+    import settings
+    from layerindex.models import Recipe
+    from django.db import transaction
+
+    logger.setLevel(options.loglevel)
+
+    try:
+        with transaction.atomic():
+            #LayerBranch.objects.filter(layermaintainer__isnull=True).delete()
+            #LayerItem.objects.filter(layerbranch__isnull=True).filter(classic=False).delete()
+            #LayerItem.objects.filter(layerbranch__isnull=True).filter(classic=False).delete()
+            for recipe in Recipe.objects.filter(filepath__startswith='../'):
+                print('Deleting erroneous recipe %s %s' % (recipe.layerbranch, recipe))
+                recipe.delete()
+
+            if options.dryrun:
+                raise DryRunRollbackException()
+    except DryRunRollbackException:
+        pass
+
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
layerindex/update_layer.py +18 -9
index d34d8a59..2b39e8ef 100644
@@ -502,6 +502,10 @@ def main():
                         if skip:
                             continue
                         if oldpath.startswith(subdir_start):
+                            if not newpath.startswith(subdir_start):
+                                logger.debug("Treating rename of %s to %s as a delete since new path is outside layer" % (oldpath, newpath))
+                                other_deletes.append(diffitem)
+                                continue
                             (oldtypename, oldfilepath, oldfilename) = recipeparse.detect_file_type(oldpath, subdir_start)
                             (newtypename, newfilepath, newfilename) = recipeparse.detect_file_type(newpath, subdir_start)
                             if oldtypename != newtypename:
@@ -684,16 +688,21 @@ def main():
                         # First, check which recipes still exist
                         layerrecipe_values = layerrecipes.values('id', 'filepath', 'filename', 'pn')
                         for v in layerrecipe_values:
-                            root = os.path.join(layerdir, v['filepath'])
-                            fullpath = os.path.join(root, v['filename'])
-                            preserve = True
-                            if os.path.exists(fullpath):
-                                for removedir in removedirs:
-                                    if fullpath.startswith(removedir):
-                                        preserve = False
-                                        break
-                            else:
+                            if v['filepath'].startswith('../'):
+                                # FIXME: These recipes were present due to a bug (not handling renames
+                                # to paths outside the layer) - this can be removed at some point in the future
                                 preserve = False
+                            else:
+                                root = os.path.join(layerdir, v['filepath'])
+                                fullpath = os.path.join(root, v['filename'])
+                                if os.path.exists(fullpath):
+                                    preserve = True
+                                    for removedir in removedirs:
+                                        if fullpath.startswith(removedir):
+                                            preserve = False
+                                            break
+                                else:
+                                    preserve = False
 
                             if preserve:
                                 # Recipe still exists, update it
Signature · 1 line
2.17.1
#2 lore raw

PATCH 2/3 Add minimal tests for update script

In reply to Paul Eggleton

Up to this point we haven't had any regression tests for the layer index, but the application (in particular the update script) has become rather complicated, and we have had a few regressions, so here are some tests. I've implemented them using pytest and pytest-django; I chose pytest since we are starting from scratch here and I like the idea of pytest's fixtures among other features. Annoyingly though because of our use of separate scripts that need to perform database operations I had to hack around some of the behaviour of pytest-django, which is clearly not designed with this kind of structure in mind, but that's taken care of now. Note that I've only considered backend testing for the moment, there's not yet a strategy for testing the UI.

To run the tests you simply run "pytest" in the root of the repository. You will need to have a working configuration set in settings.py (a database needs to be set, but won't actually be used), and if you're using MariaDB/MySQL then you'll need the READ COMMITTED transaction isolation mode selected.

At the moment there are only a few basic tests for the update script and a bunch of comments describing some we should add. The tests use a newly added synthetic meta-layerindex-test layer on git.yoctoproject.org so we can have something with known and fairly static content. I expect we will extend these tests in the near future.

---
 .gitignore           |   1 +
 pytest.ini           |   4 +
 tests/test_update.py | 195 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 200 insertions(+)
 create mode 100644 pytest.ini
 create mode 100644 tests/test_update.py
.gitignore +1 -0
index 0010cdb6..edfc2ac1 100644
@@ -1,4 +1,5 @@
 *.pyc
 *.db3
 *.swp
+.pytest_cache
 venv/
pytest.ini new file +4 -0
new file mode 100644
index 00000000..c067b059
@@ -0,0 +1,4 @@
+[pytest]
+DJANGO_SETTINGS_MODULE = settings
+norecursedirs = .git layers
+
tests/test_update.py new file +195 -0

195 lines not on this page · raw message

Signature · 1 line
2.17.1
#3 lore raw

PATCH 3/3 README: add an example virtualenv-based setup

In reply to Paul Eggleton

A while ago I was trying to help someone new get started with a development setup for the layer index and it occurred to me that the virtualenv-based environment I am using for development isn't actually covered in the documentation, so I wrote something quick for them but didn't do anything further with it until now. Here it is in more fleshed out form for the README.

Also clarify the python requirements for the update script a little bit.

---
 README | 37 +++++++++++++++++++++++++++++++++++--
 1 file changed, 35 insertions(+), 2 deletions(-)
README +35 -2
index 72dce71d..d1b1f9bd 100644
@@ -29,8 +29,41 @@ In order to make use of this application you will need:
   in settings.py and have access to the database used by the web
   application):
   * Python 2.7.6+ / Python 3.4+ to match with the version of BitBake
-    for the OpenEmbedded branch being parsed
-  * GitPython (python-git) version 2.0 or later
+    for the OpenEmbedded branch being parsed (for modern versions it's
+    Python 3.)
+  * Python dependencies as per requirements.txt (we still need Django
+    etc. here since we interact with the database through Django's ORM.)
+
+Example virtualenv-based setup for the above:
+
+Python's virtualenv provides an easy way to isolate the python dependencies
+of applications such as the layer index. Here's an example of setting up a
+virtualenv for the layer index that's particularly useful for development.
+(This assumes a Debian-based distribution, adjust accordingly for other
+distros).
+
+1. Install required host distro packages (some of these are required by
+   pip to build the dependencies; it's also assumed you want MariaDB as
+   the database backend):
+
+   sudo apt-get install virtualenv libmariadb-client-lgpl-dev build-essential python3-dev libjpeg-dev libz-dev libfreetype6-dev mariadb-server rabbitmq-server
+
+2. Work around path issues (you may not need this):
+
+   sudo ln -s /usr/bin/mariadb_config /usr/bin/mysql_config
+
+3. Create a Python 3 virtualenv (path can be anywhere you like):
+
+   virtualenv -p python3 /path/to/desired/venv
+
+4. Activate the virtualenv:
+
+   . /path/to/desired/venv/bin/activate
+
+5. Install requirements:
+
+   pip install -r requirements.txt
+
 
 Setup instructions:
 
Signature · 1 line
2.17.1