Browse Source

ref(native): Remove all references to symcaches and cficaches (#13248)

Removes all references to symcaches and cficaches in the native pipeline. 
The models, tables and the task will be removed in a follow-up PR.
Jan Michael Auer 5 years ago
parent
commit
4ae98be455

+ 5 - 2
src/sentry/deletions/defaults/project.py

@@ -44,8 +44,11 @@ class ProjectDeletionTask(ModelDeletionTask):
         # in bulk
         # Release needs to handle deletes after Group is cleaned up as the foreign
         # key is protected
-        model_list = (models.Group, models.ReleaseProject, models.ReleaseProjectEnvironment, models.ProjectDebugFile,
-                      models.ProjectSymCacheFile)
+        model_list = (
+            models.Group,
+            models.ReleaseProject,
+            models.ReleaseProjectEnvironment,
+            models.ProjectDebugFile)
         relations.extend(
             [ModelRelation(m, {'project_id': instance.id}, ModelDeletionTask) for m in model_list]
         )

+ 5 - 336
src/sentry/models/debugfile.py

@@ -20,13 +20,10 @@ import logging
 import tempfile
 
 from jsonfield import JSONField
-from django.db import models, transaction, IntegrityError
+from django.db import models
 from django.db.models.fields.related import OneToOneRel
 
-from symbolic import Archive, SymbolicError, ObjectErrorUnsupportedObject, \
-    SYMCACHE_LATEST_VERSION, SymCache, SymCacheErrorMissingDebugInfo, \
-    SymCacheErrorMissingDebugSection, CfiCache, CfiErrorMissingDebugInfo, \
-    CFICACHE_LATEST_VERSION
+from symbolic import Archive, SymbolicError, ObjectErrorUnsupportedObject
 
 from sentry import options
 from sentry.cache import default_cache
@@ -36,9 +33,7 @@ from sentry.db.models import FlexibleForeignKey, Model, \
 from sentry.models.file import File
 from sentry.reprocessing import resolve_processing_issue, \
     bump_reprocessing_revision
-from sentry.utils import metrics
 from sentry.utils.zip import safe_extract_zip
-from sentry.utils.decorators import classproperty
 
 
 logger = logging.getLogger(__name__)
@@ -192,39 +187,12 @@ class ProjectDebugFile(Model):
 
         return ''
 
-    @property
-    def supports_caches(self):
-        return ProjectSymCacheFile.computes_from(self) \
-            or ProjectCfiCacheFile.computes_from(self)
-
     @property
     def features(self):
         return frozenset((self.data or {}).get('features', []))
 
     def delete(self, *args, **kwargs):
-        dif_id = self.id
-
-        with transaction.atomic():
-            # First, delete the debug file entity. This ensures no other
-            # worker can attach caches to it. Integrity checks are deferred
-            # within this transaction, so existing caches stay intact.
-            super(ProjectDebugFile, self).delete(*args, **kwargs)
-
-            # Explicitly select referencing caches and delete them. Using
-            # the backref does not work, since `dif.id` is None after the
-            # delete.
-            symcaches = ProjectSymCacheFile.objects \
-                .filter(debug_file_id=dif_id) \
-                .select_related('cache_file')
-            for symcache in symcaches:
-                symcache.delete()
-
-            cficaches = ProjectCfiCacheFile.objects \
-                .filter(debug_file_id=dif_id) \
-                .select_related('cache_file')
-            for cficache in cficaches:
-                cficache.delete()
-
+        super(ProjectDebugFile, self).delete(*args, **kwargs)
         self.file.delete()
 
 
@@ -250,46 +218,6 @@ class ProjectCacheFile(Model):
         unique_together = (('project', 'debug_file'),)
         app_label = 'sentry'
 
-    @classproperty
-    def ignored_errors(cls):
-        """Returns a set of errors that can safely be ignored during conversion.
-        These errors should be expected by bad input data and do not indicate a
-        programming error.
-        """
-        raise NotImplementedError
-
-    @classproperty
-    def required_features(cls):
-        """Returns a set of features object files must have in order to support
-        generating this cache.
-        """
-        raise NotImplementedError
-
-    @classproperty
-    def cache_cls(cls):
-        """Returns the class of the raw cache referenced by this model. It can
-        be used to load caches from the file system or to convert object files.
-        """
-        raise NotImplementedError
-
-    @classproperty
-    def cache_name(cls):
-        """Returns the name of the cache class in lower case. Can be used for
-        file extensions, cache keys, logs, etc.
-        """
-        return cls.cache_cls.__name__.lower()
-
-    @classmethod
-    def computes_from(cls, debug_file):
-        """Indicates whether the cache can be computed from the given DIF."""
-        return set(cls.required_features) <= debug_file.features
-
-    @property
-    def outdated(self):
-        """Indicates whether this cache is outdated and needs to be recomputed.
-        """
-        raise NotImplemented
-
     def delete(self, *args, **kwargs):
         super(ProjectCacheFile, self).delete(*args, **kwargs)
         self.cache_file.delete()
@@ -301,29 +229,6 @@ class ProjectSymCacheFile(ProjectCacheFile):
     class Meta(ProjectCacheFile.Meta):
         db_table = 'sentry_projectsymcachefile'
 
-    @classproperty
-    def ignored_errors(cls):
-        return (SymCacheErrorMissingDebugSection, SymCacheErrorMissingDebugInfo)
-
-    @classproperty
-    def required_features(cls):
-        return ('debug',)
-
-    @classproperty
-    def cache_cls(cls):
-        return SymCache
-
-    @classmethod
-    def computes_from(cls, debug_file):
-        if debug_file.data is None:
-            # Compatibility with legacy DIFs before features were introduced
-            return debug_file.file_format in ('breakpad', 'macho', 'elf')
-        return super(ProjectSymCacheFile, cls).computes_from(debug_file)
-
-    @property
-    def outdated(self):
-        return self.version != SYMCACHE_LATEST_VERSION
-
 
 class ProjectCfiCacheFile(ProjectCacheFile):
     """Cache for stack unwinding information: CfiCache."""
@@ -331,22 +236,6 @@ class ProjectCfiCacheFile(ProjectCacheFile):
     class Meta(ProjectCacheFile.Meta):
         db_table = 'sentry_projectcficachefile'
 
-    @classproperty
-    def ignored_errors(cls):
-        return (CfiErrorMissingDebugInfo,)
-
-    @classproperty
-    def required_features(cls):
-        return ('unwind',)
-
-    @classproperty
-    def cache_cls(cls):
-        return CfiCache
-
-    @property
-    def outdated(self):
-        return self.version != CFICACHE_LATEST_VERSION
-
 
 def clean_redundant_difs(project, debug_id):
     """Deletes redundant debug files from the database and file storage. A debug
@@ -387,7 +276,7 @@ def create_dif_from_id(project, meta, fileobj=None, file=None):
         checksum = file.checksum
     elif fileobj is not None:
         h = hashlib.sha1()
-        while 1:
+        while True:
             chunk = fileobj.read(16384)
             if not chunk:
                 break
@@ -539,7 +428,7 @@ def create_debug_file_from_dif(to_create, project):
     return rv
 
 
-def create_files_from_dif_zip(fileobj, project, update_caches=True):
+def create_files_from_dif_zip(fileobj, project):
     """Creates all missing debug files from the given zip file.  This
     returns a list of all files created.
     """
@@ -562,16 +451,6 @@ def create_files_from_dif_zip(fileobj, project, update_caches=True):
 
         rv = create_debug_file_from_dif(to_create, project)
 
-        # Trigger generation of symcaches and cficaches to avoid dogpiling when
-        # events start coming in.
-        if update_caches:
-            from sentry.tasks.symcache_update import symcache_update
-            ids_to_update = [six.text_type(dif.debug_id) for dif in rv
-                             if dif.supports_caches]
-            if ids_to_update:
-                symcache_update.delay(project_id=project.id,
-                                      debug_ids=ids_to_update)
-
         # Uploading new dsysm changes the reprocessing revision
         bump_reprocessing_revision(project)
 
@@ -588,37 +467,6 @@ class DIFCache(object):
     def get_project_path(self, project):
         return os.path.join(self.cache_path, six.text_type(project.id))
 
-    def update_caches(self, project, debug_ids):
-        """Updates symcaches and cficaches for all debug files matching the
-        given debug ids, if the respective files support any of those caches.
-        """
-        # XXX: Worst case, this might download the same DIF twice.
-        self._get_caches_impl(project, debug_ids, ProjectSymCacheFile)
-        self._get_caches_impl(project, debug_ids, ProjectCfiCacheFile)
-
-    def get_symcaches(self, project, debug_ids, on_dif_referenced=None,
-                      with_conversion_errors=False):
-        """Loads symcaches for the given debug IDs from the file system cache or
-        blob store."""
-        cachefiles, conversion_errors = self._get_caches_impl(
-            project, debug_ids, ProjectSymCacheFile, on_dif_referenced)
-
-        symcaches = self._load_cachefiles_via_fs(project, cachefiles, SymCache)
-        if with_conversion_errors:
-            return symcaches, dict((k, v) for k, v in conversion_errors.items())
-        return symcaches
-
-    def get_cficaches(self, project, debug_ids, on_dif_referenced=None,
-                      with_conversion_errors=False):
-        """Loads cficaches for the given debug IDs from the file system cache or
-        blob store."""
-        cachefiles, conversion_errors = self._get_caches_impl(
-            project, debug_ids, ProjectCfiCacheFile, on_dif_referenced)
-        cficaches = self._load_cachefiles_via_fs(project, cachefiles, CfiCache)
-        if with_conversion_errors:
-            return cficaches, dict((k, v) for k, v in conversion_errors.items())
-        return cficaches
-
     def fetch_difs(self, project, debug_ids, features=None):
         """Given some ids returns an id to path mapping for where the
         debug symbol files are on the FS.
@@ -639,185 +487,6 @@ class DIFCache(object):
 
         return rv
 
-    def _get_caches_impl(self, project, debug_ids, cls, on_dif_referenced=None):
-        # Fetch debug files first and invoke the callback if we need
-        debug_ids = [six.text_type(debug_id).lower() for debug_id in debug_ids]
-        debug_files = ProjectDebugFile.objects.find_by_debug_ids(
-            project, debug_ids, features=cls.required_features)
-
-        # Notify the caller that we have used a symbol file
-        if on_dif_referenced is not None:
-            for debug_file in six.itervalues(debug_files):
-                on_dif_referenced(debug_file)
-
-        # Now find all the cache files we already have
-        found_ids = [d.id for d in six.itervalues(debug_files)]
-        existing_caches = cls.objects \
-            .filter(project=project, debug_file_id__in=found_ids) \
-            .select_related('cache_file', 'debug_file__debug_id')
-
-        # Check for missing and out-of-date cache files. Outdated files are
-        # removed to be re-created immediately.
-        caches = []
-        to_update = debug_files.copy()
-        for cache_file in existing_caches:
-            if cache_file.outdated:
-                cache_file.delete()
-            else:
-                debug_id = cache_file.debug_file.debug_id
-                to_update.pop(debug_id, None)
-                caches.append((debug_id, cache_file, None))
-
-        # If any cache files need to be updated, do that now
-        if to_update:
-            updated_cachefiles, conversion_errors = self._update_cachefiles(
-                project, to_update.values(), cls)
-            caches.extend(updated_cachefiles)
-        else:
-            conversion_errors = {}
-
-        return caches, conversion_errors
-
-    def _update_cachefiles(self, project, debug_files, cls):
-        rv = []
-        conversion_errors = {}
-
-        for debug_file in debug_files:
-            debug_id = debug_file.debug_id
-
-            # Find all the known bad files we could not convert last time. We
-            # use the debug identifier and file checksum to identify the source
-            # DIF for historic reasons (debug_file.id would do, too).
-            cache_key = 'scbe:%s:%s' % (debug_id, debug_file.file.checksum)
-            err = default_cache.get(cache_key)
-            if err is not None:
-                conversion_errors[debug_id] = err
-                continue
-
-            # Download the original debug symbol and convert the object file to
-            # a cache. This can either yield a cache object, an error or none of
-            # the above. THE FILE DOWNLOAD CAN TAKE SIGNIFICANT TIME.
-            with debug_file.file.getfile(as_tempfile=True) as tf:
-                file, cache, err = self._update_cachefile(debug_file, tf.name, cls)
-
-            # Store this conversion error so that we can skip subsequent
-            # conversions. There might be concurrent conversions running for the
-            # same debug file, however.
-            if err is not None:
-                default_cache.set(cache_key, err, CONVERSION_ERROR_TTL)
-                conversion_errors[debug_id] = err
-                continue
-
-            if file is not None or cache is not None:
-                rv.append((debug_id, file, cache))
-
-        return rv, conversion_errors
-
-    def _update_cachefile(self, debug_file, path, cls):
-        debug_id = debug_file.debug_id
-
-        # Skip silently if this cache cannot be computed from the given DIF
-        if not cls.computes_from(debug_file):
-            return None, None, None
-
-        # Locate the object inside the Archive. Since we have keyed debug
-        # files by debug_id, we expect a corresponding object. Otherwise, we
-        # fail silently, just like with missing symbols.
-        try:
-            archive = Archive.open(path)
-            obj = archive.get_object(debug_id=debug_id)
-            if obj is None:
-                return None, None, None
-
-            # Check features from the actual object file, if this is a legacy
-            # DIF where features have not been extracted yet.
-            if (debug_file.data or {}).get('features') is None:
-                if not set(cls.required_features) <= obj.features:
-                    return None, None, None
-
-            cache = cls.cache_cls.from_object(obj)
-        except SymbolicError as e:
-            if not isinstance(e, cls.ignored_errors):
-                logger.error('dsymfile.%s-build-error' % cls.cache_name,
-                             exc_info=True, extra=dict(debug_id=debug_id))
-
-            metrics.incr('%s.failed' % cls.cache_name, tags={
-                'error': e.__class__.__name__,
-            }, skip_internal=False)
-
-            return None, None, e.message
-
-        file = File.objects.create(name=debug_id, type='project.%s' % cls.cache_name)
-        file.putfile(cache.open_stream())
-
-        # Try to insert the new Cache into the database. This only fail if
-        # (1) another process has concurrently added the same sym cache, or if
-        # (2) the debug symbol was deleted, either due to a newer upload or via
-        # the API.
-        try:
-            with transaction.atomic():
-                return cls.objects.create(
-                    project=debug_file.project,
-                    cache_file=file,
-                    debug_file=debug_file,
-                    checksum=debug_file.file.checksum,
-                    version=cache.version,
-                ), cache, None
-        except IntegrityError:
-            file.delete()
-
-        # Check for a concurrently inserted cache and use that instead. This
-        # could have happened (1) due to a concurrent insert, or (2) a new
-        # upload that has already succeeded to compute a cache. The latter
-        # case is extremely unlikely.
-        cache_file = cls.objects \
-            .filter(project=debug_file.project, debug_file__debug_id=debug_id) \
-            .select_related('cache_file') \
-            .order_by('-id') \
-            .first()
-
-        if cache_file is not None:
-            return cache_file, None, None
-
-        # There was no new cache, indicating that the debug file has been
-        # replaced with a newer version. Another job will create the
-        # corresponding cache eventually. To prevent querying the database
-        # another time, simply use the in-memory cache for now:
-        return None, cache, None
-
-    def _load_cachefiles_via_fs(self, project, cachefiles, cls):
-        rv = {}
-        base = self.get_project_path(project)
-        cls_name = cls.__name__.lower()
-
-        for debug_id, model, cache in cachefiles:
-            # If we're given a cache instance, use that over accessing the file
-            # system or potentially even blob storage.
-            if cache is not None:
-                rv[debug_id] = cache
-                continue
-            elif model is None:
-                raise RuntimeError('missing %s file to load from fs' % cls_name)
-
-            # Try to locate a cached instance from the file system and bump the
-            # timestamp to indicate it is still being used. Otherwise, download
-            # from the blob store and place it in the cache folder.
-            cachefile_name = '%s_%s.%s' % (model.id, model.version, cls_name)
-            cachefile_path = os.path.join(base, cachefile_name)
-            try:
-                stat = os.stat(cachefile_path)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
-                model.cache_file.save_to(cachefile_path)
-            else:
-                now = int(time.time())
-                if stat.st_ctime < now - ONE_DAY:
-                    os.utime(cachefile_path, (now, now))
-
-            rv[debug_id] = cls.open(cachefile_path)
-        return rv
-
     def clear_old_entries(self):
         try:
             cache_folders = os.listdir(self.cache_path)

+ 1 - 7
src/sentry/tasks/symcache_update.py

@@ -1,7 +1,6 @@
 from __future__ import absolute_import
 
 from sentry.tasks.base import instrumented_task
-from sentry.models import Project, ProjectDebugFile
 
 
 @instrumented_task(
@@ -10,9 +9,4 @@ from sentry.models import Project, ProjectDebugFile
     soft_time_limit=60,
 )
 def symcache_update(project_id, debug_ids, **kwargs):
-    try:
-        project = Project.objects.get(id=project_id)
-    except Project.DoesNotExist:
-        return
-
-    ProjectDebugFile.difcache.update_caches(project, debug_ids)
+    pass  # Noop. TODO(ja): Remove once unused.

+ 1 - 362
tests/sentry/models/test_debugfile.py

@@ -8,11 +8,8 @@ from six import BytesIO, text_type
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.core.urlresolvers import reverse
 
-from symbolic import SYMCACHE_LATEST_VERSION
-
 from sentry.testutils import APITestCase, TestCase
-from sentry.models import debugfile, File, ProjectDebugFile, ProjectSymCacheFile, \
-    ProjectCfiCacheFile, DifMeta
+from sentry.models import debugfile, File, ProjectDebugFile, DifMeta
 
 # This is obviously a freely generated UUID and not the checksum UUID.
 # This is permissible if users want to send different UUIDs
@@ -32,45 +29,11 @@ class DebugFileTest(TestCase):
             features=['debug', 'unwind'],
         )
 
-        symcache_file = self.create_file(
-            name='baz.symcache',
-            size=42,
-            headers={'Content-Type': 'application/x-sentry-symcache'},
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-        )
-
-        symcache = ProjectSymCacheFile.objects.create(
-            project=self.project,
-            cache_file=symcache_file,
-            debug_file=dif,
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
-        cficache_file = self.create_file(
-            name='baz.cficache',
-            size=42,
-            headers={'Content-Type': 'application/x-sentry-cficache'},
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-        )
-
-        cficache = ProjectCfiCacheFile.objects.create(
-            project=self.project,
-            cache_file=cficache_file,
-            debug_file=dif,
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
         dif_id = dif.id
         dif.delete()
 
         assert not ProjectDebugFile.objects.filter(id=dif_id).exists()
         assert not File.objects.filter(id=dif.file.id).exists()
-        assert not ProjectSymCacheFile.objects.filter(id=symcache.id).exists()
-        assert not File.objects.filter(id=symcache_file.id).exists()
-        assert not ProjectCfiCacheFile.objects.filter(id=cficache.id).exists()
-        assert not File.objects.filter(id=cficache_file.id).exists()
 
     def test_find_dif_by_debug_id(self):
         debug_id1 = 'dfb8e43a-f242-3d73-a453-aeb6a777ef75'
@@ -322,327 +285,3 @@ class DebugFilesClearTest(APITestCase):
 
         # But it's gone now
         assert not os.path.isfile(difs[PROGUARD_UUID])
-
-
-class SymCacheTest(TestCase):
-    def test_get_symcache(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            features=['debug'],
-        )
-
-        file = self.create_file_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'v1.symcache'),
-            type='project.symcache'
-        )
-
-        ProjectSymCacheFile.objects.create(
-            project=self.project,
-            cache_file=file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            # XXX: This version does not correspond to the actual file version,
-            # but is sufficient to avoid update behavior
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert debug_id in symcaches
-        assert symcaches[debug_id].debug_id == debug_id
-
-    def test_miss_symcache_without_feature(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-        )
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            features=[],
-        )
-
-        # XXX: Explicit empty set denotes DIF without features. Since at least
-        # one file has declared features, get_symcaches will rather not use the
-        # other untagged file.
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert debug_id not in symcaches
-
-    def test_create_symcache_without_feature(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            file_format='macho',  # XXX: Needed for legacy compatibility check
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert debug_id in symcaches
-        assert symcaches[debug_id].debug_id == debug_id
-
-    def test_create_symcache_with_feature(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            features=['debug'],
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert debug_id in symcaches
-        assert symcaches[debug_id].debug_id == debug_id
-
-    def test_skip_symcache_without_feature(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            file_format='macho',
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert not symcaches
-
-    def test_update_symcache(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-        )
-
-        file = self.create_file_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'v1.symcache'),
-            headers={'Content-Type': 'application/x-sentry-symcache'},
-            type='project.symcache'
-        )
-
-        # Create an outdated SymCache to replace
-        old_cache = ProjectSymCacheFile.objects.create(
-            project=self.project,
-            cache_file=file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            version=1,
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_symcaches(self.project, [debug_id])
-        assert debug_id in symcaches
-        assert symcaches[debug_id].debug_id == debug_id
-        assert symcaches[debug_id].is_latest_version
-        assert not ProjectSymCacheFile.objects.filter(id=old_cache.id, version=1).exists()
-
-    def test_get_symcache_on_referenced(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            features=['debug']
-        )
-
-        referenced_ids = []
-
-        def dif_referenced(dif):
-            referenced_ids.append(dif.id)
-
-        ProjectDebugFile.difcache.get_symcaches(
-            self.project,
-            [debug_id],
-            on_dif_referenced=dif_referenced
-        )
-        assert referenced_ids == [dif.id]
-
-    def test_symcache_conversion_error(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        self.create_dif_file(
-            debug_id=debug_id,
-            features=['debug']
-        )
-
-        symcaches, errors = ProjectDebugFile.difcache.get_symcaches(
-            self.project,
-            [debug_id],
-            with_conversion_errors=True
-        )
-        assert debug_id not in symcaches
-        assert debug_id in errors
-
-    def test_delete_symcache(self):
-        dif = self.create_dif_file(
-            debug_id='dfb8e43a-f242-3d73-a453-aeb6a777ef75-feedface',
-            features=['debug']
-        )
-
-        cache_file = self.create_file(
-            name='baz.symc',
-            size=42,
-            headers={'Content-Type': 'application/x-sentry-symcache'},
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-            type='project.symcache'
-        )
-
-        symcache = ProjectSymCacheFile.objects.create(
-            project=self.project,
-            cache_file=cache_file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
-        symcache.delete()
-        assert not File.objects.filter(id=cache_file.id).exists()
-        assert not ProjectSymCacheFile.objects.filter(id=symcache.id).exists()
-
-
-class CfiCacheTest(TestCase):
-    def test_get_cficache(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            features=['unwind'],
-        )
-
-        file = self.create_file_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'v1.cficache'),
-            type='project.cficache'
-        )
-
-        ProjectCfiCacheFile.objects.create(
-            project=self.project,
-            cache_file=file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            # XXX: This version does not correspond to the actual file version,
-            # but is sufficient to avoid update behavior
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
-        cficaches = ProjectDebugFile.difcache.get_cficaches(self.project, [debug_id])
-        assert debug_id in cficaches
-
-    def test_miss_cficache_without_feature(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            features=[],
-        )
-
-        # XXX: Explicit empty set denotes DIF without features. Since at least
-        # one file has declared features, get_cficaches will rather not use the
-        # other untagged file.
-        cficaches = ProjectDebugFile.difcache.get_cficaches(self.project, [debug_id])
-        assert debug_id not in cficaches
-
-    def test_create_cficache_with_feature(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            features=['unwind'],
-        )
-
-        cficaches = ProjectDebugFile.difcache.get_cficaches(self.project, [debug_id])
-        assert debug_id in cficaches
-
-    def test_skip_cficache_without_feature(self):
-        debug_id = '67e9247c-814e-392b-a027-dbde6748fcbf'
-        self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash.dsym'),
-            debug_id=debug_id,
-            file_format='macho',
-        )
-
-        symcaches = ProjectDebugFile.difcache.get_cficaches(self.project, [debug_id])
-        assert not symcaches
-
-    def test_update_cficache(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            features=['unwind'],
-        )
-
-        file = self.create_file_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'v1.symcache'),
-            headers={'Content-Type': 'application/x-sentry-cficache'},
-            type='project.cficache'
-        )
-
-        # Create an outdated CfiCache to replace
-        old_cache = ProjectCfiCacheFile.objects.create(
-            project=self.project,
-            cache_file=file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            version=0,
-        )
-
-        cficaches = ProjectDebugFile.difcache.get_cficaches(self.project, [debug_id])
-        assert debug_id in cficaches
-        assert cficaches[debug_id].is_latest_version
-        assert not ProjectCfiCacheFile.objects.filter(id=old_cache.id, version=0).exists()
-
-    def test_get_cficache_on_referenced(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        dif = self.create_dif_from_path(
-            path=os.path.join(os.path.dirname(__file__), 'fixtures', 'crash'),
-            debug_id=debug_id,
-            features=['unwind'],
-        )
-
-        referenced_ids = []
-
-        def dif_referenced(dif):
-            referenced_ids.append(dif.id)
-
-        ProjectDebugFile.difcache.get_cficaches(
-            self.project,
-            [debug_id],
-            on_dif_referenced=dif_referenced
-        )
-        assert referenced_ids == [dif.id]
-
-    def test_cficache_conversion_error(self):
-        debug_id = '1ddb3423-950a-3646-b17b-d4360e6acfc9'
-        self.create_dif_file(
-            debug_id=debug_id,
-            features=['unwind'],
-        )
-
-        cficaches, errors = ProjectDebugFile.difcache.get_cficaches(
-            self.project,
-            [debug_id],
-            with_conversion_errors=True
-        )
-        assert debug_id not in cficaches
-        assert debug_id in errors
-
-    def test_delete_cficache(self):
-        dif = self.create_dif_file(
-            debug_id='dfb8e43a-f242-3d73-a453-aeb6a777ef75-feedface',
-            features=['unwind'],
-        )
-
-        cache_file = self.create_file(
-            name='baz.symc',
-            size=42,
-            headers={'Content-Type': 'application/x-sentry-cficache'},
-            checksum='dc1e3f3e411979d336c3057cce64294f3420f93a',
-            type='project.cficache'
-        )
-
-        cficache = ProjectCfiCacheFile.objects.create(
-            project=self.project,
-            cache_file=cache_file,
-            debug_file=dif,
-            checksum=dif.file.checksum,
-            version=SYMCACHE_LATEST_VERSION,
-        )
-
-        cficache.delete()
-        assert not File.objects.filter(id=cache_file.id).exists()
-        assert not ProjectCfiCacheFile.objects.filter(id=cficache.id).exists()