Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mutation testing #115

Open
snarfed opened this issue Jul 25, 2017 · 4 comments
Open

add mutation testing #115

snarfed opened this issue Jul 25, 2017 · 4 comments

Comments

@snarfed
Copy link
Owner

snarfed commented Jul 25, 2017

...via something like https://github.com/sixty-north/cosmic-ray. mutation testing is very cool. description in https://cosmic-ray.readthedocs.io/en/latest/#theory

long pole is that cosmic ray is python 3 only, which implies that this depends on #114. sixty-north/cosmic-ray#101 has more details about cosmic ray's lack of python 2 support.

@snarfed
Copy link
Owner Author

snarfed commented Mar 31, 2018

we're on python 3 now! looking forward to trying this.

@snarfed
Copy link
Owner Author

snarfed commented Apr 2, 2018

done! results:

total jobs: 1113
complete: 1113 (100.00%)
survival rate: 4.49%

config file:

# https://cosmic-ray.readthedocs.io/en/latest/quickstart.html#configuration-file

module: granary

baseline: 10

exclude-modules:
    - granary.test

test-runner:
  name: unittest
  args: granary/test/

execution-engine:
  name: local

full report (debugging details in sixty-north/cosmic-ray#352):

job ID 751ca09b450d4c3b995500e7cc352e2c:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_NotIn_Is 0
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -59,7 +59,7 @@
 
 def _activity_or_object(activity):
     "Returns the base item we care about, activity or activity['object'].\n\n  Used in :func:`activity_to_json()` and :func:`activities_to_html()`.\n  "
-    if (activity.get('object') and (activity.get('verb') not in source.VERBS_WITH_OBJECT)):
+    if (activity.get('object') and (activity.get('verb') is source.VERBS_WITH_OBJECT)):
         return activity['object']
     return activity

job ID d7556ddf202c43f9abffa600ade13d0c:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceOrWithAnd 7
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -70,7 +70,7 @@
     obj_type = (source.object_type(obj) or default_object_type)
     if (obj_type == 'post'):
         primary = obj.get('object', {})
-        obj_type = (source.object_type(primary) or default_object_type)
+        obj_type = (source.object_type(primary) and default_object_type)
     else:
         primary = obj
     name = primary.get('displayName', primary.get('title'))

job ID e9f99780983f49ee824b68821814506b:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceOrWithAnd 17
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -85,7 +85,7 @@
     for prop in ('attachments', 'tags'):
         for elem in get_list(primary, prop):
             attachments[elem.get('objectType')].append(elem)
-    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) or object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=False, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=False, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
+    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) and object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=False, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=False, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
     tags = (obj.get('tags', []) or get_first(obj, 'object', {}).get('tags', []))
     ret['properties']['category'] = []
     for tag in tags:

job ID 35e6d2407f4f4a748da02d19ec8d226b:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceTrueFalse 3
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -85,7 +85,7 @@
     for prop in ('attachments', 'tags'):
         for elem in get_list(primary, prop):
             attachments[elem.get('objectType')].append(elem)
-    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) or object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=False, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=False, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
+    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) or object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=True, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=False, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
     tags = (obj.get('tags', []) or get_first(obj, 'object', {}).get('tags', []))
     ret['properties']['category'] = []
     for tag in tags:

job ID 679f16baadaf4a6aa8bf9f80df1381a0:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceTrueFalse 4
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -85,7 +85,7 @@
     for prop in ('attachments', 'tags'):
         for elem in get_list(primary, prop):
             attachments[elem.get('objectType')].append(elem)
-    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) or object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=False, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=False, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
+    ret = {'type': ((AS_TO_MF2_TYPE.get(obj_type) or [entry_class]) if isinstance(entry_class, basestring) else list(entry_class)), 'properties': {'uid': [(obj.get('id') or '')], 'numeric-id': [(obj.get('numeric_id') or '')], 'name': [name], 'nickname': [(obj.get('username') or '')], 'summary': [summary], 'url': (list((object_urls(obj) or object_urls(primary))) + obj.get('upstreamDuplicates', [])), 'photo': dedupe_urls((get_urls(attachments, 'image', 'image') + get_urls(primary, 'image'))), 'video': dedupe_urls((get_urls(attachments, 'video', 'stream') + get_urls(primary, 'stream'))), 'audio': get_urls(attachments, 'audio', 'stream'), 'published': [obj.get('published', primary.get('published', ''))], 'updated': [obj.get('updated', primary.get('updated', ''))], 'content': [{'value': xml.sax.saxutils.unescape(primary.get('content', '')), 'html': render_content(primary, include_location=False, synthesize_content=synthesize_content)}], 'in-reply-to': util.trim_nulls([o.get('url') for o in in_reply_tos]), 'author': [object_to_json(author, trim_nulls=False, default_object_type='person')], 'location': [object_to_json(primary.get('location', {}), trim_nulls=True, default_object_type='place')], 'comment': [object_to_json(c, trim_nulls=False, entry_class='h-cite') for c in obj.get('replies', {}).get('items', [])], 'start': [primary.get('startTime')], 'end': [primary.get('endTime')]}, 'children': [object_to_json(a, trim_nulls=False, entry_class=['u-quotation-of', 'h-cite']) for a in (attachments['note'] + attachments['article'])]}
     tags = (obj.get('tags', []) or get_first(obj, 'object', {}).get('tags', []))
     ret['properties']['category'] = []
     for tag in tags:

job ID 85b5f2c644634774a78d5ff3609c17c7:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceTrueFalse 7
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -98,7 +98,7 @@
     if is_rsvp:
         ret['properties']['rsvp'] = [obj_type[len('rsvp-'):]]
     elif (obj_type == 'invite'):
-        invitee = object_to_json(obj.get('object'), trim_nulls=False, default_object_type='person')
+        invitee = object_to_json(obj.get('object'), trim_nulls=True, default_object_type='person')
         ret['properties']['invitee'] = [invitee]
     for (type, prop) in (('favorite', 'like'), ('like', 'like'), ('share', 'repost')):
         if (obj_type == type):

job ID 3e8b31cbe0d741859fa95a44e59d6f9f:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceTrueFalse 8
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -103,7 +103,7 @@
     for (type, prop) in (('favorite', 'like'), ('like', 'like'), ('share', 'repost')):
         if (obj_type == type):
             objs = get_list(obj, 'object')
-            ret['properties'][(prop + '-of')] = [(o['url'] if (('url' in o) and (set(o.keys()) <= set(['url', 'objectType']))) else object_to_json(o, trim_nulls=False, entry_class='h-cite')) for o in objs]
+            ret['properties'][(prop + '-of')] = [(o['url'] if (('url' in o) and (set(o.keys()) <= set(['url', 'objectType']))) else object_to_json(o, trim_nulls=True, entry_class='h-cite')) for o in objs]
         else:
             ret['properties'][prop] = [object_to_json(t, trim_nulls=False, entry_class='h-cite') for t in tags if (source.object_type(t) == type)]
     lat = long = None

job ID bea1deff4cf04359a51bf38d07b43467:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_In_IsNot 1
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -103,7 +103,7 @@
     for (type, prop) in (('favorite', 'like'), ('like', 'like'), ('share', 'repost')):
         if (obj_type == type):
             objs = get_list(obj, 'object')
-            ret['properties'][(prop + '-of')] = [(o['url'] if (('url' in o) and (set(o.keys()) <= set(['url', 'objectType']))) else object_to_json(o, trim_nulls=False, entry_class='h-cite')) for o in objs]
+            ret['properties'][(prop + '-of')] = [(o['url'] if (('url' is not o) and (set(o.keys()) <= set(['url', 'objectType']))) else object_to_json(o, trim_nulls=False, entry_class='h-cite')) for o in objs]
         else:
             ret['properties'][prop] = [object_to_json(t, trim_nulls=False, entry_class='h-cite') for t in tags if (source.object_type(t) == type)]
     lat = long = None

job ID ed97ceb5bb09489f82aceb27ff0f09ca:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceTrueFalse 9
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -105,7 +105,7 @@
             objs = get_list(obj, 'object')
             ret['properties'][(prop + '-of')] = [(o['url'] if (('url' in o) and (set(o.keys()) <= set(['url', 'objectType']))) else object_to_json(o, trim_nulls=False, entry_class='h-cite')) for o in objs]
         else:
-            ret['properties'][prop] = [object_to_json(t, trim_nulls=False, entry_class='h-cite') for t in tags if (source.object_type(t) == type)]
+            ret['properties'][prop] = [object_to_json(t, trim_nulls=True, entry_class='h-cite') for t in tags if (source.object_type(t) == type)]
     lat = long = None
     position = ISO_6709_RE.match((primary.get('position') or ''))
     if position:

job ID 3db7d4456a9d4941b26e5abc0830d322:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceOrWithAnd 23
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -124,7 +124,7 @@
 
 def json_to_object(mf2, actor=None, fetch_mf2=False):
     'Converts microformats2 JSON to an ActivityStreams object.\n\n  Args:\n    mf2: dict, decoded JSON microformats2 object\n    actor: optional author AS actor object. usually comes from a rel="author"\n      link. if mf2 has its own author, that will override this.\n    fetch_mf2: boolean, whether to fetch additional pages via HTTP if necessary,\n      e.g. to determine authorship: https://indieweb.org/authorship\n\n  Returns:\n    dict, ActivityStreams object\n  '
-    if ((not mf2) or (not isinstance(mf2, dict))):
+    if ((not mf2) and (not isinstance(mf2, dict))):
         return {}
     mf2 = copy.copy(mf2)
     props = mf2.setdefault('properties', {})

job ID d3ee375384ce4a9eb3d2410628e10d5a:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ZeroIterationLoop 4
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -153,7 +153,7 @@
     if rsvp:
         as_verb = ('rsvp-%s' % rsvp)
     in_reply_tos = get_string_urls(props.get('in-reply-to', []))
-    for url in in_reply_tos:
+    for url in []:
         if re.match('^https?://github.com/[^/]+/[^/]+(/issues)?/?$', url):
             as_type = 'issue'

job ID b66c3d4a386249de95b4700fefde8f68:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_In_IsNot 4
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -160,7 +160,7 @@
     def absolute_urls(prop):
         return [url for url in get_string_urls(props.get(prop, [])) if urllib.parse.urlparse(url).netloc]
     urls = (props.get('url') and get_string_urls(props.get('url')))
-    attachments = [json_to_object(quote) for quote in (mf2.get('children', []) + props.get('quotation-of', [])) if (isinstance(quote, dict) and ('h-cite' in set(quote.get('type', []))))]
+    attachments = [json_to_object(quote) for quote in (mf2.get('children', []) + props.get('quotation-of', [])) if (isinstance(quote, dict) and ('h-cite' is not set(quote.get('type', []))))]
     for type in ('audio', 'video'):
         attachments.extend(({'objectType': type, 'stream': {'url': url}} for url in get_string_urls(props.get(type, []))))
     obj = {'id': prop.get('uid'), 'objectType': as_type, 'verb': as_verb, 'published': prop.get('published', ''), 'updated': prop.get('updated', ''), 'startTime': prop.get('start'), 'endTime': prop.get('end'), 'displayName': get_text(prop.get('name')), 'summary': get_text(prop.get('summary')), 'content': get_html(prop.get('content')), 'url': (urls[0] if urls else None), 'urls': ([{'value': u} for u in urls] if (urls and (len(urls) > 1)) else None), 'image': [{'url': url} for url in dedupe_urls((absolute_urls('photo') + absolute_urls('featured')))], 'stream': [{'url': url} for url in absolute_urls('video')], 'location': json_to_object(prop.get('location')), 'replies': {'items': [json_to_object(c) for c in props.get('comment', [])]}, 'tags': [({'objectType': 'hashtag', 'displayName': cat} if isinstance(cat, basestring) else json_to_object(cat)) for cat in props.get('category', [])], 'attachments': attachments}

job ID 172cd68866ba4dbea3ed75084b42fc08:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_Gt_NotEq 0
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -163,7 +163,7 @@
     attachments = [json_to_object(quote) for quote in (mf2.get('children', []) + props.get('quotation-of', [])) if (isinstance(quote, dict) and ('h-cite' in set(quote.get('type', []))))]
     for type in ('audio', 'video'):
         attachments.extend(({'objectType': type, 'stream': {'url': url}} for url in get_string_urls(props.get(type, []))))
-    obj = {'id': prop.get('uid'), 'objectType': as_type, 'verb': as_verb, 'published': prop.get('published', ''), 'updated': prop.get('updated', ''), 'startTime': prop.get('start'), 'endTime': prop.get('end'), 'displayName': get_text(prop.get('name')), 'summary': get_text(prop.get('summary')), 'content': get_html(prop.get('content')), 'url': (urls[0] if urls else None), 'urls': ([{'value': u} for u in urls] if (urls and (len(urls) > 1)) else None), 'image': [{'url': url} for url in dedupe_urls((absolute_urls('photo') + absolute_urls('featured')))], 'stream': [{'url': url} for url in absolute_urls('video')], 'location': json_to_object(prop.get('location')), 'replies': {'items': [json_to_object(c) for c in props.get('comment', [])]}, 'tags': [({'objectType': 'hashtag', 'displayName': cat} if isinstance(cat, basestring) else json_to_object(cat)) for cat in props.get('category', [])], 'attachments': attachments}
+    obj = {'id': prop.get('uid'), 'objectType': as_type, 'verb': as_verb, 'published': prop.get('published', ''), 'updated': prop.get('updated', ''), 'startTime': prop.get('start'), 'endTime': prop.get('end'), 'displayName': get_text(prop.get('name')), 'summary': get_text(prop.get('summary')), 'content': get_html(prop.get('content')), 'url': (urls[0] if urls else None), 'urls': ([{'value': u} for u in urls] if (urls and (len(urls) != 1)) else None), 'image': [{'url': url} for url in dedupe_urls((absolute_urls('photo') + absolute_urls('featured')))], 'stream': [{'url': url} for url in absolute_urls('video')], 'location': json_to_object(prop.get('location')), 'replies': {'items': [json_to_object(c) for c in props.get('comment', [])]}, 'tags': [({'objectType': 'hashtag', 'displayName': cat} if isinstance(cat, basestring) else json_to_object(cat)) for cat in props.get('category', [])], 'attachments': attachments}
     interpreted = mf2util.interpret({'items': [mf2]}, None)
     if interpreted:
         loc = interpreted.get('location')

job ID a8b4c90581bc44d287f79e93a13f93b3:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceAndWithOr 17
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -170,7 +170,7 @@
         if loc:
             obj['location']['objectType'] = 'place'
             (lat, lng) = (loc.get('latitude'), loc.get('longitude'))
-            if (lat and lng):
+            if (lat or lng):
                 try:
                     obj['location'].update({'latitude': float(lat), 'longitude': float(lng)})
                 except ValueError:

job ID 54b8ee2910ef40728186a90d4353dec3:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_NotIn_NotEq 1
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -179,7 +179,7 @@
         objects = []
         for target in itertools.chain.from_iterable((props.get(field, []) for field in ('like', 'like-of', 'repost', 'repost-of', 'in-reply-to', 'invitee'))):
             t = (json_to_object(target) if isinstance(target, dict) else {'url': target})
-            if (t not in objects):
+            if (t != objects):
                 objects.append(t)
         obj.update({'object': (objects[0] if (len(objects) == 1) else objects), 'actor': author})
     else:

job ID b37d3abbb2ae4f7eaf5f06756984163c:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/NumberReplacer 15
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -230,7 +230,7 @@
     if rsvp:
         if (not props.get('name')):
             props['name'] = [{'yes': 'is attending.', 'no': 'is not attending.', 'maybe': 'might attend.'}.get(rsvp)]
-        props['name'][0] = ('<data class="p-rsvp" value="%s">%s</data>' % (rsvp, props['name'][0]))
+        props['name'][0] = ('<data class="p-rsvp" value="%s">%s</data>' % (rsvp, props['name'][-1]))
     elif (props.get('invitee') and (not props.get('name'))):
         props['name'] = ['invited']
     children = []

job ID e213bc5b5c0741cebb8d70925ebecae0:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_NotIn_IsNot 2
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -257,7 +257,7 @@
     tags = [('<span class="u-category">%s</span>' % cat) for cat in cats if isinstance(cat, basestring)]
     comments_html = '\n'.join((json_to_html(c, ['p-comment']) for c in props.get('comment', [])))
     for verb in ('like', 'repost'):
-        if ((verb + '-of') not in props):
+        if ((verb + '-of') is not props):
             vals = props.get(verb, [])
             if (vals and isinstance(vals[0], dict)):
                 children += [json_to_html(v, [('u-' + verb)]) for v in vals]

job ID 6a005e48360c40469beb7b67a7ed1432:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/NumberReplacer 17
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -259,7 +259,7 @@
     for verb in ('like', 'repost'):
         if ((verb + '-of') not in props):
             vals = props.get(verb, [])
-            if (vals and isinstance(vals[0], dict)):
+            if (vals and isinstance(vals[-1], dict)):
                 children += [json_to_html(v, [('u-' + verb)]) for v in vals]
     children += [json_to_html(c) for c in obj.get('children', [])]
     location = prop.get('location')

job ID 6fde419f4bdb4c38a2433e5e2234ed46:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceAndWithOr 26
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -269,7 +269,7 @@
     start = props.get('start', [])
     end = props.get('end', [])
     event_times += [('  <time class="dt-start">%s</time>' % time) for time in start]
-    if (start and end):
+    if (start or end):
         event_times.append('  to')
     event_times += [('  <time class="dt-end">%s</time>' % time) for time in end]
     return HENTRY.substitute(prop, published=maybe_datetime(prop.get('published'), 'dt-published'), updated=maybe_datetime(prop.get('updated'), 'dt-updated'), types=' '.join((parent_props + types)), author=hcard_to_html(author, ['p-author']), location=hcard_to_html(location, ['p-location']), categories='\n'.join((people + tags)), attachments='\n'.join(attachments), in_reply_tos=in_reply_tos, invitees='\n'.join([hcard_to_html(i, ['p-invitee']) for i in props.get('invitee', [])]), content=content_html, content_classes=' '.join(content_classes), comments=comments_html, children='\n'.join(children), linked_name=maybe_linked_name(props), summary=summary, event_times='\n'.join(event_times))

job ID be1f8761dfde4dceb145500ac49be72a:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceAndWithOr 30
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -297,7 +297,7 @@
         if (id and (id in seen_ids)):
             continue
         seen_ids.add(id)
-        if (('startIndex' in t) and ('length' in t)):
+        if (('startIndex' in t) or ('length' in t)):
             mentions.append(t)
         else:
             tags.setdefault(source.object_type(t), []).append(t)



job ID 4814bb601a3b428d957a7c2069646e26:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_NotIn_Is 4
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -319,7 +319,7 @@
         content += _render_attachments((atts + tags.pop('article', [])), obj)
     obj_type = source.object_type(obj)
     for (as_type, verb) in (('favorite', 'Favorites'), ('like', 'Likes'), ('share', 'Shared')):
-        if ((not synthesize_content) or (obj_type != as_type) or ('object' not in obj) or ('content' in obj)):
+        if ((not synthesize_content) or (obj_type != as_type) or ('object' is obj) or ('content' in obj)):
             continue
         targets = get_list(obj, 'object')
         if (not targets):

job ID 48bf3f011a2b49ea9ef878badcc179c9:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceBinaryOperator_Mod_Add 11
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -330,7 +330,7 @@
             else:
                 author = target.get('author', target.get('actor', {}))
                 if ((obj_type == 'share') and ('url' in obj) and re.search('^https?://(?:www\\.|mobile\\.)?twitter\\.com/', obj.get('url'))):
-                    content += ('RT <a href="%s">@%s</a> ' % (target.get('url', '#'), author.get('username')))
+                    content += ('RT <a href="%s">@%s</a> ' + (target.get('url', '#'), author.get('username')))
                 else:
                     author = {k: v for (k, v) in author.items() if (k != 'image')}
                     content += ('%s <a href="%s">%s</a> by %s' % (verb, target.get('url', '#'), target.get('displayName', target.get('title', 'a post')), hcard_to_html(object_to_json(author, default_object_type='person'))))

job ID 62cb22b52f6b4fa9a40e5afa849797b9:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/ReplaceComparisonOperator_NotIn_NotEq 5
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -338,7 +338,7 @@
             break
         break
     if (render_attachments and (obj.get('verb') == 'share')):
-        atts = [a for a in obj.get('object', {}).get('attachments', []) if (a.get('objectType') not in ('note', 'article'))]
+        atts = [a for a in obj.get('object', {}).get('attachments', []) if (a.get('objectType') != ('note', 'article'))]
         content += _render_attachments(atts, obj)
     loc = obj.get('location')
     if (include_location and loc):

job ID a5b47eec08e346d4800919a1848cc750:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/AddNot 61
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -410,7 +410,7 @@
 def author_display_name(hcard):
     'Returns a human-readable string display name for an h-card object.'
     name = None
-    if hcard:
+    if (not hcard):
         prop = first_props(hcard.get('properties'))
         name = (prop.get('name') or prop.get('uid'))
     return (name if name else 'Unknown')

job ID 0dbd3d16692043f89d7e66971ea5f39b:survived:granary.microformats2
command: cosmic-ray worker granary.microformats2 core/AddNot 62
--- mutation diff ---
--- a/Users/ryan/src/granary/granary/microformats2.py
+++ b/Users/ryan/src/granary/granary/microformats2.py
@@ -413,7 +413,7 @@
     if hcard:
         prop = first_props(hcard.get('properties'))
         name = (prop.get('name') or prop.get('uid'))
-    return (name if name else 'Unknown')
+    return (name if (not name) else 'Unknown')
 
 def maybe_linked_name(props):
     'Returns the HTML for a p-name with an optional u-url inside.\n\n  Args:\n    props: *multiply-valued* properties dict\n\n  Returns:\n    string HTML\n  '

total jobs: 1113
complete: 1113 (100.00%)
survival rate: 4.49%

@snarfed
Copy link
Owner Author

snarfed commented Apr 9, 2018

i also tried to do this for https://github.com/snarfed/webutil , but got stuck since only util.py is python 3, and the cosmic-ray config lets me run a specific directory's tests, but not a specific file. (i got it running, but it reported that almost all mutants survived, which they didn't.)

the fix would be to add a new cosmic ray config option and pass it to TestLoader.discover() here.

@snarfed
Copy link
Owner Author

snarfed commented Apr 9, 2018

i skimmed the granary mutations. they generally all look real, but not high priority, so i'm deprioritizing adding tests for them, at least for now.

...also i expect granary's cosmic ray config is buggy too, since we only got mutant reports in microformats2.py, no other files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant