# Mutations the ledger tests must catch. One row each: file | exact text | replacement | why
# `python3 .github/scripts/run_tests.py --mutants --only ledger` applies each row to a scratch copy
# and fails when the tests still pass. The text must occur exactly once in the file.
gwt_ledger.py | if s in self.alias: | if False: | an alias (fs) resolves to its canonical id (familysearch-index)
gwt_ledger.py | canon = registry.canon if registry else key | canon = key | check finds a row logged under an alias of the source asked for
gwt_ledger.py | if self.alias.get(a, sid) != sid: | if False: | a registry refuses an alias that already names another source
gwt_ledger.py | near = [] if new_source else self.near(slug) | near = [] | a source close to a known name (familysea) exits 2 unless --new-source
gwt_ledger.py | if near: | if True: | a source far from every known name is kept as its slug, with no registry file
gwt_ledger.py | if s.endswith('-*'): | if False: | coverage takes the shipped registry as its sources file (a -* row is no column)
gwt_ledger.py | and not (sid in self.sources and not row['kind']) |  | a user row extending a shipped id may leave kind empty
gwt_ledger.py | encoding='utf-8-sig') as f:  # -sig | encoding='utf-8') as f:  # -sig | a registry file with a byte order mark reads
gwt_ledger.py | if result == 'none' and not control: | if False: | a none without --control-ok is stored as unproven
gwt_ledger.py | if bad or not oks: | if False: | a --control-ok file must be a passing control run (no void, no SHORT)
gwt_ledger.py | SEARCHED = ('hit', 'none', 'partial', 'out-of-coverage') | SEARCHED = ('hit', 'none', 'partial', 'out-of-coverage', 'unproven') | an unproven row never counts as a search that ran
gwt_ledger.py | a.add_argument('--result', required=True, choices=RESULTS) | a.add_argument('--result', required=True) | add refuses a result outside the enum (found, negative)
gwt_ledger.py | result = word if word in RESULTS else SYNONYMS.get(word) | result = word if word in RESULTS else SYNONYMS.get(word, word) | import-tsv refuses a result that is neither a result nor a synonym
gwt_ledger.py | return bool((src['to'] and int(src['to']) < lo) or (src['from'] and int(src['from']) > hi)) | return False | coverage marks n/a a source whose years cannot hold the person's life
gwt_ledger.py | if r['status'] not in CLOSED and r['next_date'] and r['next_date'] <= on | if r['next_date'] and r['next_date'] <= on | a closed or refused request is never due
gwt_ledger.py | t = totals[(r[col] or '-', r['currency'].upper() or '-')] | t = totals[(r[col] or '-', '-')] | costs never add two currencies together
gwt_ledger.py | .replace(';', '\\;').replace(',', '\\,') | .replace(';', ';').replace(',', ',') | .ics text escapes ; and , (RFC 5545 3.3.11)
gwt_ledger.py | if '/' in ref or os.sep in ref or re.search(r'\.(txt|tsv|log|out|json)$', ref, re.I): | if False: | a --control-ok path with a typo is refused, never stored as a run id
gwt_ledger.py | if key(ref) in NOT_PROOF or not normalize(ref): | if False: | --control-ok no (or -) is not a control run
gwt_ledger.py | checked[control] = control_problem(control) | checked[control] = None | import-tsv checks each row's control like add
gwt_ledger.py | control = clean(v.get('control')) or clean(args.control_ok) | control = clean(v.get('control')) | import-tsv --control-ok vouches for the rows with no control
gwt_ledger.py | if same(row) in seen: | if False: | importing the same file twice adds nothing
gwt_ledger.py | out = {gid} | owned.get(gid, set()) if gid else set() | out = {gid} if gid else set() | coverage counts a person key that starts with <gramps-id>- for that person
gwt_ledger.py | if name and names[name] == 1: | if name: | coverage never gives one namesake the other's rows
gwt_ledger.py | if unmatched: | if False: | coverage says how many ledger keys match nobody, so an empty matrix is not read as not searched
gwt_ledger.py | ids = [l.split('\t', 1)[0] for l in lines[1 if lines and lines[0].startswith('id\t') else 0:]] | ids = [l.split('\t', 1)[0] for l in lines] | request update refuses the header word id
gwt_ledger.py | return format(d, 'f')  # 1e3 is stored as 1000 | return str(d)  # 1e3 is stored as 1000 | a cost of 1e3 is stored as 1000
gwt_ledger.py | if bad or not oks: | if bad: | a --control-ok file with no ok control line (empty, or a search's output) is no proof
gwt_ledger.py | bad = [l for l in lines if l.startswith('void\t') or | bad = [l for l in lines if False or | a --control-ok file with a void line is no proof, whatever its ok lines say
gwt_ledger.py | return bool((src['to'] and int(src['to']) < lo) or (src['from'] and int(src['from']) > hi)) | return bool((src['from'] and int(src['from']) > hi)) | coverage marks n/a a source whose records end before the person was born
gwt_ledger.py | if any(r['result'] == 'hit' for r in rows): | if False: | coverage shows an older hit over a newer none
gwt_ledger.py | for n in range(len(parts), 0, -1)) | for n in range(1, len(parts) + 1)) | a key of a suffixed id (i0042-b-...) goes to I0042-b, never to I0042
gwt_ledger.py | if columns == COLUMNS: | if False: | a none from before 1.1.0 (no control column) reads as unproven, so check does not count it as done
gwt_ledger.py | rows = live(everything)  # | rows = everything  # | a voided row no longer counts for check
gwt_ledger.py | return [r for r in rows if r['result'] != VOID and r['id'] not in gone] | return [r for r in rows if r['id'] not in gone] | a void row itself is no search: stats and due leave it out
gwt_ledger.py | seen[r['line']] += 1 | seen[r['line']] = 1 | a new row identical to a voided one counts again
gwt_ledger.py | find(rows, args.person, source, args.query, exact=True, registry=reg) | find(rows, args.person, source, args.query, registry=reg) | void retracts the identical normalized query only, never a close one
gwt_ledger.py | for _, r in find(everything, args.person | for _, r in find(rows, args.person | list still shows the void rows and what they retracted
gwt_ledger.py | 'voided' if r['id'] in gone else '' | '' | list marks a voided row, and not its identical re-add
gwt_ledger.py | seen = {same(r) for r in live([r for path | seen = {same(r) for r in ([r for path | import-tsv imports a row identical to a voided one, so a re-search counts again
gwt_ledger.py | if lost:  # | if False:  # | an edited voided line warns on stderr that its row counts again
gwt_ledger.py | '; already voided by %s' % ' '.join(r['id'] for _, r in done) if done else '' | '' | a second void says the search is already voided, so a retry reads apart from a typo
gwt_ledger.py | label = args.label or targets[0]['person_label'] | label = args.label | a void row with no --label takes the label of the first row it retracts
gwt_ledger.py | VOIDS.match(r['evidence']) if r['result'] == VOID else None | VOIDS.match(r['evidence']) | only a void row retracts: a hit whose evidence reads like a void retracts nothing
gwt_ledger.py | ' '.join(r['id'] for r in targets), args.reason | ' '.join(r['id'] for r in targets[:1]), args.reason | a void retracts every lane's row of the search, not just the first
gwt_ledger.py | for name in ('query', 'person', 'reason'): | for name in ('query', 'person'): | a void reason with nothing in it is refused
gwt_ledger.py | found = unless() if unless else None | found = None | add skips a row the ledger already holds (same person, source, query, date and result)
